@poolzin/poolbot-office 0.1.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/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # PoolBot Office
2
+
3
+ > Visual monitoring & management frontend for PoolBot Multi-Agent system
4
+
5
+ **Version:** 0.1.0
6
+ **Status:** 🚧 In Development
7
+ **Target Release:** v1.0.0 (Q2 2026)
8
+
9
+ ---
10
+
11
+ ## 🎯 Overview
12
+
13
+ PoolBot Office is a comprehensive visual dashboard for monitoring and managing your PoolBot Multi-Agent system. It features:
14
+
15
+ - **2D Office View** - Isometric floor plan with agent desks and collaboration visualization
16
+ - **3D Office View** - Full 3D scene with animated characters and holographic displays
17
+ - **Chat Interface** - Real-time chat with agents, streaming support, and message history
18
+ - **Console Dashboard** - Complete management for agents, channels, skills, and cron jobs
19
+
20
+ ---
21
+
22
+ ## 🚀 Quick Start
23
+
24
+ ```bash
25
+ # Install dependencies
26
+ cd apps/office
27
+ pnpm install
28
+
29
+ # Development mode
30
+ pnpm dev
31
+
32
+ # Build for production
33
+ pnpm build
34
+
35
+ # Start production server
36
+ pnpm start
37
+ ```
38
+
39
+ ---
40
+
41
+ ## 📋 Features
42
+
43
+ ### Virtual Office
44
+ - [x] 2D isometric floor plan
45
+ - [x] 3D scene with React Three Fiber
46
+ - [x] Agent avatars with status animations
47
+ - [x] Collaboration lines (message flow visualization)
48
+ - [x] Furniture (desks, chairs, plants)
49
+ - [x] Spawn/despawn effects
50
+
51
+ ### Chat Interface
52
+ - [x] Bottom-docked chat bar
53
+ - [x] Real-time message streaming
54
+ - [x] Agent selector with status
55
+ - [x] Message history with search
56
+ - [x] Copy message functionality
57
+ - [x] Typing indicators
58
+
59
+ ### Console Dashboard
60
+ - [x] Overview stats (agents, channels, skills, cron)
61
+ - [x] Alert banners
62
+ - [x] Quick actions
63
+ - [x] Agent management (CRUD)
64
+ - [x] Channel management (connect/disconnect)
65
+ - [x] Skills marketplace (install/uninstall)
66
+ - [x] Cron job management (run/pause/delete)
67
+ - [x] Settings (gateway, theme, language)
68
+
69
+ ---
70
+
71
+ ## 🛠️ Tech Stack
72
+
73
+ | Layer | Technology |
74
+ |-------|------------|
75
+ | Build Tool | Vite 6 |
76
+ | UI Framework | React 19 |
77
+ | 2D Rendering | SVG + CSS |
78
+ | 3D Rendering | React Three Fiber + Drei |
79
+ | State Management | React useState/useEffect |
80
+ | Styling | CSS + Inline styles |
81
+ | Language | TypeScript 5 |
82
+
83
+ ---
84
+
85
+ ## 📁 Project Structure
86
+
87
+ ```
88
+ apps/office/
89
+ ├── bin/ # CLI executables
90
+ │ ├── poolbot-office.js
91
+ │ ├── poolbot-office-config.js
92
+ │ └── poolbot-office-server.js
93
+ ├── src/
94
+ │ ├── components/
95
+ │ │ ├── chat/ # Chat interface components
96
+ │ │ ├── console/ # Console dashboard pages
97
+ │ │ ├── office-2d/ # 2D office components
98
+ │ │ └── office-3d/ # 3D office components
99
+ │ ├── gateway/ # WebSocket client
100
+ │ ├── styles/ # Global styles
101
+ │ ├── App.tsx # Main app component
102
+ │ └── main.tsx # Entry point
103
+ ├── public/ # Static assets
104
+ ├── index.html # HTML template
105
+ ├── package.json
106
+ ├── tsconfig.json
107
+ └── vite.config.ts
108
+ ```
109
+
110
+ ---
111
+
112
+ ## 🔧 CLI Options
113
+
114
+ ```bash
115
+ poolbot-office [options]
116
+
117
+ Options:
118
+ -t, --token <token> Gateway auth token (auto-detected)
119
+ -g, --gateway <url> Gateway WebSocket URL
120
+ -p, --port <port> Server port (default: 5180)
121
+ --host <host> Bind address (default: 0.0.0.0)
122
+ -h, --help Show help
123
+ ```
124
+
125
+ ---
126
+
127
+ ## 📊 Progress
128
+
129
+ **Current Status:** 60% Complete (9/15 phases)
130
+
131
+ ### Completed Phases
132
+ - [x] Phase 1: Setup do Projeto
133
+ - [x] Phase 2: CLI e Servidor
134
+ - [x] Phase 3: Gateway WebSocket Client
135
+ - [x] Phase 4: 2D Office Layout
136
+ - [x] Phase 5: 3D Office Layout
137
+ - [x] Phase 6: Chat Interface
138
+ - [x] Phase 7: Console Dashboard
139
+ - [x] Phase 8: Polish & Integration
140
+ - [x] Phase 9: Documentation
141
+
142
+ ### Remaining Phases
143
+ - [ ] Phase 10: Testing (Unit/E2E)
144
+ - [ ] Phase 11: Release Preparation
145
+
146
+ ---
147
+
148
+ ## 📝 License
149
+
150
+ MIT License - see [LICENSE](../../LICENSE) for details.
151
+
152
+ ---
153
+
154
+ ## 🙏 Acknowledgments
155
+
156
+ PoolBot Office is inspired by and based on the architecture of [OpenClaw Office](https://github.com/openclaw/openclaw-office).
157
+
158
+ ---
159
+
160
+ **Documentation:** https://docs.molt.bot/office
161
+ **GitHub:** https://github.com/poolbot/poolbot
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PoolBot Office Configuration
5
+ *
6
+ * Handles CLI argument parsing and configuration management
7
+ */
8
+
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
12
+
13
+ const DEFAULT_PORT = 5180;
14
+ const DEFAULT_HOST = "0.0.0.0";
15
+ const DEFAULT_GATEWAY = "ws://localhost:18789";
16
+
17
+ export function parseArgs() {
18
+ const args = process.argv.slice(2);
19
+ const parsed = {};
20
+
21
+ for (let i = 0; i < args.length; i++) {
22
+ const arg = args[i];
23
+ switch (arg) {
24
+ case "-h":
25
+ case "--help":
26
+ parsed.help = true;
27
+ break;
28
+ case "-t":
29
+ case "--token":
30
+ parsed.token = args[++i];
31
+ break;
32
+ case "-g":
33
+ case "--gateway":
34
+ parsed.gateway = args[++i];
35
+ break;
36
+ case "-p":
37
+ case "--port":
38
+ parsed.port = parseInt(args[++i], 10);
39
+ break;
40
+ case "--host":
41
+ parsed.host = args[++i];
42
+ break;
43
+ }
44
+ }
45
+
46
+ return parsed;
47
+ }
48
+
49
+ export function detectPoolBotToken() {
50
+ try {
51
+ const credentialsPath = join(homedir(), ".poolbot", "credentials", "gateway.json");
52
+ if (!existsSync(credentialsPath)) {
53
+ return null;
54
+ }
55
+ const credentials = JSON.parse(readFileSync(credentialsPath, "utf-8"));
56
+ return credentials.token || null;
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ export function resolveConfig() {
63
+ const args = parseArgs();
64
+ const homeDir = homedir();
65
+ const officeConfigPath = join(homeDir, ".poolbot", "office-config.json");
66
+
67
+ // Load persisted config if exists
68
+ let persistedGatewayUrl;
69
+ if (existsSync(officeConfigPath)) {
70
+ try {
71
+ const persisted = JSON.parse(readFileSync(officeConfigPath, "utf-8"));
72
+ persistedGatewayUrl = persisted.gatewayUrl;
73
+ } catch {
74
+ // Ignore corrupted config
75
+ }
76
+ }
77
+
78
+ // Determine gateway URL
79
+ let gatewayUrl = args.gateway || persistedGatewayUrl || DEFAULT_GATEWAY;
80
+ const shouldPersistGatewayUrl = !!args.gateway && !persistedGatewayUrl;
81
+
82
+ // Determine token
83
+ let token = args.token || detectPoolBotToken() || undefined;
84
+
85
+ // Extract token from gateway URL if present (for remote gateways)
86
+ if (gatewayUrl.includes("?token=")) {
87
+ const url = new URL(gatewayUrl.replace("ws://", "http://").replace("wss://", "https://"));
88
+ const urlToken = url.searchParams.get("token");
89
+ if (urlToken) {
90
+ token = urlToken;
91
+ // Remove token from URL for clean storage
92
+ url.searchParams.delete("token");
93
+ gatewayUrl = url.toString().replace("http://", "ws://").replace("https://", "wss://");
94
+ }
95
+ }
96
+
97
+ return {
98
+ gatewayUrl,
99
+ token,
100
+ port: args.port || DEFAULT_PORT,
101
+ host: args.host || DEFAULT_HOST,
102
+ officeConfigPath,
103
+ shouldPersistGatewayUrl,
104
+ };
105
+ }
106
+
107
+ export function writePersistedOfficeConfig(gatewayUrl, configPath) {
108
+ try {
109
+ const configDir = join(configPath, "..");
110
+ if (!existsSync(configDir)) {
111
+ mkdirSync(configDir, { recursive: true });
112
+ }
113
+ writeFileSync(configPath, JSON.stringify({ gatewayUrl }, null, 2));
114
+ } catch (error) {
115
+ console.error("Failed to persist office config:", error);
116
+ }
117
+ }
118
+
119
+ export function printHelp() {
120
+ console.log(`
121
+ PoolBot Office — Visual Agent Monitoring & Management
122
+
123
+ Usage:
124
+ poolbot-office [options]
125
+
126
+ Options:
127
+ -t, --token <token> Gateway auth token (auto-detected from ~/.poolbot)
128
+ -g, --gateway <url> Gateway WebSocket URL (default: ws://localhost:18789)
129
+ -p, --port <port> Server port (default: 5180)
130
+ --host <host> Bind address (default: 0.0.0.0)
131
+ -h, --help Show help
132
+
133
+ Examples:
134
+ poolbot-office # Auto-detect local gateway
135
+ poolbot-office --token abc123 # Explicit token
136
+ poolbot-office -g ws://remote:18789 # Remote gateway
137
+ poolbot-office -p 8080 # Custom port
138
+
139
+ Remote Gateway Support:
140
+ PoolBot Office supports both local and remote gateways.
141
+ For remote gateways (Aliyun, Tencent Cloud, etc.), use:
142
+
143
+ poolbot-office -g "ws://your-remote-gateway:18789?token=your-token"
144
+
145
+ The token is automatically extracted from the URL.
146
+
147
+ Documentation:
148
+ https://docs.molt.bot/office
149
+ `);
150
+ }
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PoolBot Office Server
5
+ *
6
+ * HTTP server for serving the Office UI and proxying WebSocket connections
7
+ */
8
+
9
+ import { createServer } from "node:http";
10
+ import { readFileSync, existsSync } from "node:fs";
11
+ import { join, extname } from "node:path";
12
+ import { networkInterfaces } from "node:os";
13
+
14
+ /**
15
+ * MIME types for static files
16
+ */
17
+ const MIME_TYPES = {
18
+ ".html": "text/html; charset=utf-8",
19
+ ".css": "text/css; charset=utf-8",
20
+ ".js": "application/javascript; charset=utf-8",
21
+ ".json": "application/json; charset=utf-8",
22
+ ".png": "image/png",
23
+ ".jpg": "image/jpeg",
24
+ ".svg": "image/svg+xml",
25
+ ".ico": "image/x-icon",
26
+ ".woff": "font/woff",
27
+ ".woff2": "font/woff2",
28
+ };
29
+
30
+ /**
31
+ * Get file content type
32
+ */
33
+ function getContentType(filePath) {
34
+ const ext = extname(filePath).toLowerCase();
35
+ return MIME_TYPES[ext] || "application/octet-stream";
36
+ }
37
+
38
+ /**
39
+ * Send JSON response
40
+ */
41
+ function sendJson(res, status, body) {
42
+ res.statusCode = status;
43
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
44
+ res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
45
+ res.end(JSON.stringify(body));
46
+ }
47
+
48
+ /**
49
+ * Send static file
50
+ */
51
+ function sendStaticFile(res, filePath) {
52
+ if (!existsSync(filePath)) {
53
+ res.statusCode = 404;
54
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
55
+ res.end("Not Found");
56
+ return;
57
+ }
58
+
59
+ try {
60
+ const content = readFileSync(filePath);
61
+ res.statusCode = 200;
62
+ res.setHeader("Content-Type", getContentType(filePath));
63
+ res.setHeader("Cache-Control", "public, max-age=31536000");
64
+ res.end(content);
65
+ } catch {
66
+ res.statusCode = 500;
67
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
68
+ res.end("Internal Server Error");
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Handle health check requests
74
+ */
75
+ function handleHealthCheck(req, res) {
76
+ if (req.url === "/healthz") {
77
+ res.statusCode = 200;
78
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
79
+ res.end("ok");
80
+ return;
81
+ }
82
+
83
+ if (req.url === "/health") {
84
+ sendJson(res, 200, {
85
+ status: "ok",
86
+ service: "poolbot-office",
87
+ version: "0.1.0",
88
+ timestamp: Date.now(),
89
+ });
90
+ return;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Handle gateway config requests
96
+ */
97
+ function handleGatewayConfig(req, res, config) {
98
+ if (req.url === "/api/gateway-config") {
99
+ sendJson(res, 200, {
100
+ gatewayUrl: config.gatewayUrl,
101
+ hasToken: !!config.token,
102
+ });
103
+ return;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Create Office HTTP server
109
+ */
110
+ export function createOfficeServer(options) {
111
+ const { config, distDir } = options;
112
+
113
+ const server = createServer((req, res) => {
114
+ // Health checks
115
+ if (req.url === "/healthz" || req.url === "/health") {
116
+ handleHealthCheck(req, res);
117
+ return;
118
+ }
119
+
120
+ // Gateway config API
121
+ if (req.url?.startsWith("/api/")) {
122
+ handleGatewayConfig(req, res, config);
123
+ return;
124
+ }
125
+
126
+ // Static file serving
127
+ let requestPath = req.url === "/" ? "/index.html" : req.url;
128
+
129
+ // Remove query parameters
130
+ requestPath = requestPath.split("?")[0];
131
+
132
+ const filePath = join(distDir, requestPath);
133
+
134
+ // Security: prevent directory traversal
135
+ if (!filePath.startsWith(distDir)) {
136
+ res.statusCode = 403;
137
+ res.setHeader("Content-Type", "text/plain; charset=utf-8");
138
+ res.end("Forbidden");
139
+ return;
140
+ }
141
+
142
+ sendStaticFile(res, filePath);
143
+ });
144
+
145
+ return { server };
146
+ }
147
+
148
+ /**
149
+ * Format startup summary
150
+ */
151
+ export function formatStartupSummary(config) {
152
+ const localIp = getLocalIpAddress();
153
+
154
+ const lines = [
155
+ "",
156
+ " ┌─────────────────────────────────────────────────┐",
157
+ " │ │",
158
+ " │ PoolBot Office is running! │",
159
+ " │ │",
160
+ " └─────────────────────────────────────────────────┘",
161
+ "",
162
+ ` ➜ Local: http://localhost:${config.port}/`,
163
+ ` ➜ Network: http://${localIp}:${config.port}/`,
164
+ "",
165
+ ` Gateway: ${config.gatewayUrl}`,
166
+ ` Token: ${config.token ? "✓ Configured" : "✗ Not configured"}`,
167
+ "",
168
+ " ─────────────────────────────────────────────────",
169
+ "",
170
+ " Quick Start:",
171
+ " 1. Open the URL above in your browser",
172
+ " 2. Configure gateway connection if needed",
173
+ " 3. Start monitoring your agents!",
174
+ "",
175
+ " Documentation: https://docs.molt.bot/office",
176
+ "",
177
+ ];
178
+
179
+ return lines.join("\n");
180
+ }
181
+
182
+ /**
183
+ * Get local IP address
184
+ */
185
+ function getLocalIpAddress() {
186
+ try {
187
+ const nets = networkInterfaces();
188
+
189
+ for (const name of Object.keys(nets)) {
190
+ const netArray = nets[name];
191
+ if (!netArray) continue;
192
+
193
+ for (const net of netArray) {
194
+ const family = net.family;
195
+ const internal = net.internal;
196
+ const address = net.address;
197
+
198
+ // Skip internal (loopback) and non-IPv4 addresses
199
+ if (internal || family !== "IPv4") continue;
200
+
201
+ return address;
202
+ }
203
+ }
204
+ } catch {
205
+ // Fallback
206
+ }
207
+
208
+ return "localhost";
209
+ }
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PoolBot Office CLI
5
+ *
6
+ * Visual monitoring & management frontend for PoolBot Multi-Agent system
7
+ */
8
+
9
+ import { fileURLToPath } from "node:url";
10
+ import { resolve } from "node:path";
11
+ import {
12
+ printHelp,
13
+ resolveConfig,
14
+ parseArgs,
15
+ writePersistedOfficeConfig,
16
+ } from "./poolbot-office-config.js";
17
+ import { createOfficeServer, formatStartupSummary } from "./poolbot-office-server.js";
18
+
19
+ const __dirname = fileURLToPath(new URL(".", import.meta.url));
20
+ const distDir = resolve(__dirname, "..", "dist");
21
+
22
+ const args = parseArgs();
23
+ if (args.help) {
24
+ printHelp();
25
+ process.exit(0);
26
+ }
27
+
28
+ const config = resolveConfig();
29
+ if (config.shouldPersistGatewayUrl) {
30
+ writePersistedOfficeConfig(config.gatewayUrl, config.officeConfigPath);
31
+ }
32
+
33
+ const { server } = createOfficeServer({ config, distDir });
34
+
35
+ server.listen(config.port, config.host, () => {
36
+ console.log(formatStartupSummary(config));
37
+ });
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@poolzin/poolbot-office",
3
+ "version": "0.1.0",
4
+ "description": "Visual monitoring & management frontend for PoolBot Multi-Agent system",
5
+ "keywords": [
6
+ "ai-agents",
7
+ "dashboard",
8
+ "monitoring",
9
+ "multi-agent",
10
+ "office",
11
+ "poolbot",
12
+ "react",
13
+ "visualization",
14
+ "vite"
15
+ ],
16
+ "homepage": "https://github.com/poolbot/poolbot/tree/main/apps/office",
17
+ "bugs": {
18
+ "url": "https://github.com/poolbot/poolbot/issues"
19
+ },
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/poolbot/poolbot.git",
24
+ "directory": "apps/office"
25
+ },
26
+ "bin": {
27
+ "poolbot-office": "bin/poolbot-office.js"
28
+ },
29
+ "files": [
30
+ "dist/",
31
+ "bin/",
32
+ "LICENSE",
33
+ "README.md"
34
+ ],
35
+ "type": "module",
36
+ "scripts": {
37
+ "build": "tsc -b && vite build",
38
+ "check": "pnpm lint && pnpm format:check",
39
+ "dev": "vite",
40
+ "format": "oxfmt --write src/",
41
+ "format:check": "oxfmt --check src/",
42
+ "lint": "oxlint src/",
43
+ "preview": "vite preview",
44
+ "start": "node ./bin/poolbot-office.js",
45
+ "test": "vitest run --passWithNoTests",
46
+ "test:watch": "vitest --passWithNoTests",
47
+ "typecheck": "tsc --noEmit"
48
+ },
49
+ "dependencies": {
50
+ "@react-three/drei": "^10.7.7",
51
+ "@react-three/fiber": "^9.5.0",
52
+ "@react-three/postprocessing": "^3.0.4",
53
+ "i18next": "^25.8.13",
54
+ "i18next-browser-languagedetector": "^8.2.1",
55
+ "immer": "^10.1.1",
56
+ "lucide-react": "^0.575.0",
57
+ "react": "^19.1.0",
58
+ "react-dom": "^19.1.0",
59
+ "react-i18next": "^16.5.4",
60
+ "react-markdown": "^10.1.0",
61
+ "react-router-dom": "^7.13.1",
62
+ "react-textarea-autosize": "^8.5.9",
63
+ "recharts": "^2.15.0",
64
+ "remark-gfm": "^4.0.1",
65
+ "three": "^0.183.1",
66
+ "zustand": "^5.0.0"
67
+ },
68
+ "devDependencies": {
69
+ "@tailwindcss/vite": "^4.1.0",
70
+ "@testing-library/jest-dom": "^6.6.0",
71
+ "@testing-library/react": "^16.3.0",
72
+ "@types/react": "^19.1.0",
73
+ "@types/react-dom": "^19.1.0",
74
+ "@types/three": "^0.183.1",
75
+ "@vitejs/plugin-react": "^4.5.0",
76
+ "fake-indexeddb": "^6.2.5",
77
+ "jsdom": "^26.1.0",
78
+ "tailwindcss": "^4.1.0",
79
+ "typescript": "^5.8.0",
80
+ "vite": "^6.3.0",
81
+ "vitest": "^3.1.0"
82
+ },
83
+ "engines": {
84
+ "node": ">=22"
85
+ }
86
+ }