@expressots/studio 4.0.0-preview.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/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # @expressots/studio
2
+
3
+ ExpressoTS Studio - Developer Experience Platform. The main package that orchestrates the Studio Agent and Web UI.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -D @expressots/studio
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### CLI Usage
14
+
15
+ ```bash
16
+ # Start Studio
17
+ npx expressots-studio start
18
+
19
+ # With options
20
+ npx expressots-studio start --port 3333 --agent-port 3334
21
+
22
+ # Show project info
23
+ npx expressots-studio info
24
+
25
+ # Clean Studio data
26
+ npx expressots-studio clean
27
+ ```
28
+
29
+ ### Programmatic Usage
30
+
31
+ ```typescript
32
+ import { Studio } from '@expressots/studio';
33
+
34
+ const studio = new Studio({
35
+ uiPort: 3333,
36
+ agentPort: 3334,
37
+ dbPath: '.studio/studio.db',
38
+ srcPath: './src',
39
+ });
40
+
41
+ await studio.start();
42
+
43
+ // Studio is now running
44
+ // UI: http://localhost:3333
45
+ // Agent: ws://localhost:3334
46
+
47
+ // To stop
48
+ await studio.stop();
49
+ ```
50
+
51
+ ### Integration with ExpressoTS CLI
52
+
53
+ ```bash
54
+ # Via ExpressoTS CLI (when integrated)
55
+ expressots studio
56
+ ```
57
+
58
+ ## CLI Commands
59
+
60
+ ### `start` (default)
61
+
62
+ Start ExpressoTS Studio.
63
+
64
+ Options:
65
+ - `-p, --port <port>` - UI port (default: 3333)
66
+ - `-a, --agent-port <port>` - Agent WebSocket port (default: 3334)
67
+ - `-d, --db-path <path>` - Database path (default: .studio/studio.db)
68
+ - `--src <path>` - Source directory to scan (default: ./src)
69
+ - `--no-browser` - Don't open browser automatically
70
+
71
+ ### `info`
72
+
73
+ Show information about the current project including discovered routes.
74
+
75
+ Options:
76
+ - `--src <path>` - Source directory to scan (default: ./src)
77
+
78
+ ### `clean`
79
+
80
+ Remove all Studio data (database, cache).
81
+
82
+ Options:
83
+ - `-d, --db-path <path>` - Database path (default: .studio/studio.db)
84
+
85
+ ## Configuration
86
+
87
+ You can also configure Studio via `expressots.config.ts`:
88
+
89
+ ```typescript
90
+ // expressots.config.ts
91
+ export default {
92
+ studio: {
93
+ uiPort: 3333,
94
+ agentPort: 3334,
95
+ dbPath: '.studio/studio.db',
96
+ },
97
+ };
98
+ ```
99
+
100
+ ## Architecture
101
+
102
+ ```
103
+ ┌─────────────────────────────────────────────────────────┐
104
+ │ ExpressoTS Studio │
105
+ │ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ │
106
+ │ │ Web UI │ │ Studio Agent │ │ MCP Server │ │
107
+ │ │ :3333 │←→│ :3334 │←→│ (AI tools) │ │
108
+ │ └─────────────┘ └──────────────┘ └────────────┘ │
109
+ │ ↓ ↓ │
110
+ │ Your ExpressoTS App (instrumented) │
111
+ └─────────────────────────────────────────────────────────┘
112
+ ```
113
+
114
+ ## What's Included
115
+
116
+ - **Studio Agent**: Instrumentation, route discovery, request recording
117
+ - **Web UI**: Dashboard with trace visualization, architecture map, metrics
118
+ - **MCP Server**: AI-powered code generation tools (optional)
119
+
120
+ ## License
121
+
122
+ MIT © ExpressoTS
package/dist/cli.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ExpressoTS Studio CLI
4
+ * Main entry point for launching the Studio
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;GAGG"}
package/dist/cli.js ADDED
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ExpressoTS Studio CLI
4
+ * Main entry point for launching the Studio
5
+ */
6
+ import { Command } from 'commander';
7
+ import chalk from 'chalk';
8
+ import ora from 'ora';
9
+ import open from 'open';
10
+ import { Studio } from './studio.js';
11
+ const program = new Command();
12
+ program
13
+ .name('expressots-studio')
14
+ .description('ExpressoTS Studio - Developer Experience Platform')
15
+ .version('0.1.0');
16
+ program
17
+ .command('start')
18
+ .alias('s')
19
+ .description('Start ExpressoTS Studio')
20
+ .option('-p, --port <port>', 'UI port', '3333')
21
+ .option('-a, --agent-port <port>', 'Agent WebSocket port', '3334')
22
+ .option('-d, --db-path <path>', 'Database path', '.studio/studio.db')
23
+ .option('--no-browser', 'Do not open browser automatically')
24
+ .option('--src <path>', 'Source directory to scan', './src')
25
+ .option('--standalone', 'Run in standalone mode (starts own agent)', false)
26
+ .action(async (options) => {
27
+ try {
28
+ const studio = new Studio({
29
+ uiPort: parseInt(options.port, 10),
30
+ agentPort: parseInt(options.agentPort, 10),
31
+ dbPath: options.dbPath,
32
+ srcPath: options.src,
33
+ standalone: options.standalone,
34
+ });
35
+ await studio.start();
36
+ const agentStatus = options.standalone
37
+ ? chalk.yellow('Standalone')
38
+ : studio.isAgentConnected()
39
+ ? chalk.green('Connected')
40
+ : chalk.gray('Waiting for app...');
41
+ console.log('');
42
+ console.log(chalk.green(' Studio is running'));
43
+ console.log('');
44
+ console.log(` UI ${chalk.cyan(`http://localhost:${options.port}`)}`);
45
+ console.log(` Agent ${agentStatus} ${chalk.gray(`ws://localhost:${options.agentPort}`)}`);
46
+ console.log('');
47
+ console.log(chalk.gray(' Press Ctrl+C to stop'));
48
+ console.log('');
49
+ if (options.browser !== false) {
50
+ await open(`http://localhost:${options.port}`);
51
+ }
52
+ process.on('SIGINT', async () => {
53
+ console.log('');
54
+ await studio.stop();
55
+ console.log(chalk.gray(' Studio stopped'));
56
+ process.exit(0);
57
+ });
58
+ process.on('SIGTERM', async () => {
59
+ await studio.stop();
60
+ process.exit(0);
61
+ });
62
+ }
63
+ catch (error) {
64
+ console.error(chalk.red('\n Failed to start Studio:'), error instanceof Error ? error.message : error);
65
+ process.exit(1);
66
+ }
67
+ });
68
+ program
69
+ .command('info')
70
+ .description('Show information about the current project')
71
+ .option('--src <path>', 'Source directory to scan', './src')
72
+ .action(async (options) => {
73
+ const { RouteScanner } = await import('@expressots/studio-agent');
74
+ const spinner = ora('Scanning project...').start();
75
+ try {
76
+ const scanner = new RouteScanner(options.src);
77
+ const structure = await scanner.scan();
78
+ spinner.succeed(chalk.green('Project scanned successfully'));
79
+ console.log('');
80
+ console.log(chalk.cyan('📁 Project Structure:'));
81
+ console.log('');
82
+ console.log(chalk.white(` Controllers: ${structure.controllers.length}`));
83
+ console.log(chalk.white(` Services: ${structure.services.length}`));
84
+ console.log(chalk.white(` Providers: ${structure.providers.length}`));
85
+ console.log(chalk.white(` Middleware: ${structure.middleware.length}`));
86
+ console.log('');
87
+ if (structure.controllers.length > 0) {
88
+ console.log(chalk.cyan('🛣️ Routes:'));
89
+ console.log('');
90
+ const routes = scanner.getRoutes();
91
+ for (const route of routes) {
92
+ const methodColor = route.method === 'GET' ? chalk.green :
93
+ route.method === 'POST' ? chalk.blue :
94
+ route.method === 'PUT' ? chalk.yellow :
95
+ route.method === 'DELETE' ? chalk.red :
96
+ chalk.white;
97
+ console.log(` ${methodColor(route.method.padEnd(7))} ${route.path}`);
98
+ }
99
+ console.log('');
100
+ }
101
+ }
102
+ catch (error) {
103
+ spinner.fail(chalk.red('Failed to scan project'));
104
+ console.error(error instanceof Error ? error.message : error);
105
+ process.exit(1);
106
+ }
107
+ });
108
+ program
109
+ .command('clean')
110
+ .description('Clean Studio data')
111
+ .option('-d, --db-path <path>', 'Database path', '.studio/studio.db')
112
+ .action(async (options) => {
113
+ const fs = await import('fs');
114
+ const path = await import('path');
115
+ const dbPath = path.resolve(process.cwd(), options.dbPath);
116
+ const studioDir = path.dirname(dbPath);
117
+ if (fs.existsSync(studioDir)) {
118
+ fs.rmSync(studioDir, { recursive: true });
119
+ console.log(chalk.green('✓'), 'Studio data cleaned');
120
+ }
121
+ else {
122
+ console.log(chalk.yellow('No Studio data found'));
123
+ }
124
+ });
125
+ // Default to start if no command is provided
126
+ program
127
+ .action(() => {
128
+ const startCmd = program.commands.find((cmd) => cmd.name() === 'start');
129
+ startCmd?.parse();
130
+ });
131
+ program.parse();
132
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @expressots/studio
3
+ *
4
+ * ExpressoTS Studio - Developer Experience Platform
5
+ * Main orchestrator package that launches the agent and UI
6
+ */
7
+ export { Studio } from './studio.js';
8
+ export type { StudioConfig } from './studio.js';
9
+ export { StudioAgent, RouteScanner, RequestRecorder } from '@expressots/studio-agent';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @expressots/studio
3
+ *
4
+ * ExpressoTS Studio - Developer Experience Platform
5
+ * Main orchestrator package that launches the agent and UI
6
+ */
7
+ export { Studio } from './studio.js';
8
+ // Re-export from agent for convenience
9
+ export { StudioAgent, RouteScanner, RequestRecorder } from '@expressots/studio-agent';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,uCAAuC;AACvC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Studio Orchestrator
3
+ *
4
+ * Two modes:
5
+ * 1. UI-only mode (default): Just serves the UI, connects to agent in user's app
6
+ * 2. Standalone mode: Starts its own agent (for demos or when not using core integration)
7
+ */
8
+ import { StudioAgent } from '@expressots/studio-agent';
9
+ export interface StudioConfig {
10
+ uiPort: number;
11
+ agentPort: number;
12
+ dbPath: string;
13
+ srcPath: string;
14
+ serviceName?: string;
15
+ /** If true, starts a standalone agent (default: false, just serves UI) */
16
+ standalone?: boolean;
17
+ }
18
+ export declare class Studio {
19
+ private config;
20
+ private agent;
21
+ private uiApp;
22
+ private uiServer;
23
+ private agentClient;
24
+ private agentConnected;
25
+ constructor(config?: Partial<StudioConfig>);
26
+ /** Start the Studio */
27
+ start(): Promise<void>;
28
+ /** Stop the Studio */
29
+ stop(): Promise<void>;
30
+ /** Connect to an existing agent running in the user's app */
31
+ private connectToAgent;
32
+ /** Start the Studio Agent (standalone mode only) */
33
+ private startAgent;
34
+ /** Start the UI server */
35
+ private startUIServer;
36
+ /** Find the bundled UI dist path */
37
+ private findUIDistPath;
38
+ /**
39
+ * Resolve the brand icon SVG as a base64 data URI. Used by the dev-mode
40
+ * fallback HTML, which is served as a catch-all when the UI bundle is
41
+ * missing — so we can't rely on `/expressots-icon.svg` being reachable.
42
+ * Returns a tiny inline SVG placeholder if the asset is missing.
43
+ */
44
+ private getIconDataUri;
45
+ /** Get development mode HTML */
46
+ private getDevModeHTML;
47
+ /** Check if agent is connected */
48
+ isAgentConnected(): boolean;
49
+ /** Get the Studio Agent instance (standalone mode only) */
50
+ getAgent(): StudioAgent | null;
51
+ /** Get configuration */
52
+ getConfig(): StudioConfig;
53
+ }
54
+ //# sourceMappingURL=studio.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"studio.d.ts","sourceRoot":"","sources":["../src/studio.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,qBAAa,MAAM;IACjB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,KAAK,CAAwB;IACrC,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,cAAc,CAAkB;gBAE5B,MAAM,GAAE,OAAO,CAAC,YAAY,CAAM;IAW9C,uBAAuB;IACjB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B,sBAAsB;IAChB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB3B,6DAA6D;YAC/C,cAAc;IAsC5B,oDAAoD;YACtC,UAAU;IAYxB,0BAA0B;YACZ,aAAa;IAkC3B,oCAAoC;IACpC,OAAO,CAAC,cAAc;IAQtB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IA0BtB,gCAAgC;IAChC,OAAO,CAAC,cAAc;IA0HtB,kCAAkC;IAClC,gBAAgB,IAAI,OAAO;IAI3B,2DAA2D;IAC3D,QAAQ,IAAI,WAAW,GAAG,IAAI;IAI9B,wBAAwB;IACxB,SAAS,IAAI,YAAY;CAG1B"}
package/dist/studio.js ADDED
@@ -0,0 +1,308 @@
1
+ /**
2
+ * Studio Orchestrator
3
+ *
4
+ * Two modes:
5
+ * 1. UI-only mode (default): Just serves the UI, connects to agent in user's app
6
+ * 2. Standalone mode: Starts its own agent (for demos or when not using core integration)
7
+ */
8
+ import express from 'express';
9
+ import { createServer } from 'http';
10
+ import { io as SocketIOClient } from 'socket.io-client';
11
+ import path from 'path';
12
+ import fs from 'fs';
13
+ import { fileURLToPath } from 'url';
14
+ import { StudioAgent } from '@expressots/studio-agent';
15
+ export class Studio {
16
+ config;
17
+ agent = null;
18
+ uiApp = null;
19
+ uiServer = null;
20
+ agentClient = null;
21
+ agentConnected = false;
22
+ constructor(config = {}) {
23
+ this.config = {
24
+ uiPort: config.uiPort ?? 3333,
25
+ agentPort: config.agentPort ?? 3334,
26
+ dbPath: config.dbPath ?? '.studio/studio.db',
27
+ srcPath: config.srcPath ?? './src',
28
+ serviceName: config.serviceName ?? 'expressots-app',
29
+ standalone: config.standalone ?? false,
30
+ };
31
+ }
32
+ /** Start the Studio */
33
+ async start() {
34
+ if (this.config.standalone) {
35
+ await this.startAgent();
36
+ }
37
+ else {
38
+ // UI-only mode: Try to connect to existing agent
39
+ await this.connectToAgent();
40
+ }
41
+ // Start the UI server
42
+ await this.startUIServer();
43
+ }
44
+ /** Stop the Studio */
45
+ async stop() {
46
+ // Disconnect from agent
47
+ if (this.agentClient) {
48
+ this.agentClient.disconnect();
49
+ this.agentClient = null;
50
+ }
51
+ // Stop UI server
52
+ if (this.uiServer) {
53
+ await new Promise((resolve) => {
54
+ this.uiServer.close(() => resolve());
55
+ });
56
+ this.uiServer = null;
57
+ }
58
+ // Stop agent (only if we started it)
59
+ if (this.agent) {
60
+ await this.agent.stop();
61
+ this.agent = null;
62
+ }
63
+ }
64
+ /** Connect to an existing agent running in the user's app */
65
+ async connectToAgent() {
66
+ const agentUrl = `http://localhost:${this.config.agentPort}`;
67
+ return new Promise((resolve) => {
68
+ const probe = SocketIOClient(agentUrl, {
69
+ transports: ['websocket'],
70
+ reconnection: false,
71
+ timeout: 3000,
72
+ });
73
+ this.agentClient = probe;
74
+ const cleanup = (connected) => {
75
+ this.agentConnected = connected;
76
+ probe.removeAllListeners();
77
+ probe.disconnect();
78
+ if (this.agentClient === probe) {
79
+ this.agentClient = null;
80
+ }
81
+ resolve();
82
+ };
83
+ const timeout = setTimeout(() => {
84
+ if (!this.agentConnected) {
85
+ cleanup(false);
86
+ }
87
+ }, 3000);
88
+ probe.on('connect', () => {
89
+ clearTimeout(timeout);
90
+ cleanup(true);
91
+ });
92
+ probe.on('connect_error', () => {
93
+ // Wait for timeout fallback; reconnection is disabled.
94
+ });
95
+ });
96
+ }
97
+ /** Start the Studio Agent (standalone mode only) */
98
+ async startAgent() {
99
+ this.agent = new StudioAgent({
100
+ port: this.config.agentPort,
101
+ dbPath: this.config.dbPath,
102
+ serviceName: this.config.serviceName,
103
+ enableRecording: true,
104
+ enableProfiling: true,
105
+ });
106
+ await this.agent.start();
107
+ }
108
+ /** Start the UI server */
109
+ async startUIServer() {
110
+ this.uiApp = express();
111
+ // Try to find the built UI files
112
+ const uiDistPath = this.findUIDistPath();
113
+ if (uiDistPath && fs.existsSync(uiDistPath)) {
114
+ // Serve static files from the UI build
115
+ this.uiApp.use(express.static(uiDistPath));
116
+ // SPA fallback
117
+ this.uiApp.get('*', (_req, res) => {
118
+ res.sendFile(path.join(uiDistPath, 'index.html'));
119
+ });
120
+ }
121
+ else {
122
+ // Development mode - show placeholder
123
+ this.uiApp.get('*', (_req, res) => {
124
+ res.send(this.getDevModeHTML());
125
+ });
126
+ }
127
+ this.uiServer = createServer(this.uiApp);
128
+ return new Promise((resolve, reject) => {
129
+ this.uiServer.listen(this.config.uiPort, () => {
130
+ resolve();
131
+ });
132
+ this.uiServer.on('error', (err) => {
133
+ reject(err);
134
+ });
135
+ });
136
+ }
137
+ /** Find the bundled UI dist path */
138
+ findUIDistPath() {
139
+ // The UI is bundled into this package at build time and lives at
140
+ // `<package>/dist/ui/` next to the orchestrator's compiled JS.
141
+ const here = path.dirname(fileURLToPath(import.meta.url));
142
+ const candidate = path.resolve(here, 'ui');
143
+ return fs.existsSync(candidate) ? candidate : null;
144
+ }
145
+ /**
146
+ * Resolve the brand icon SVG as a base64 data URI. Used by the dev-mode
147
+ * fallback HTML, which is served as a catch-all when the UI bundle is
148
+ * missing — so we can't rely on `/expressots-icon.svg` being reachable.
149
+ * Returns a tiny inline SVG placeholder if the asset is missing.
150
+ */
151
+ getIconDataUri() {
152
+ try {
153
+ const here = path.dirname(fileURLToPath(import.meta.url));
154
+ const candidates = [
155
+ path.resolve(here, 'ui', 'expressots-icon.svg'),
156
+ path.resolve(here, '..', 'public', 'expressots-icon.svg'),
157
+ path.resolve(here, '..', '..', 'public', 'expressots-icon.svg'),
158
+ ];
159
+ for (const c of candidates) {
160
+ if (fs.existsSync(c)) {
161
+ const svg = fs.readFileSync(c, 'utf8');
162
+ return `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`;
163
+ }
164
+ }
165
+ }
166
+ catch {
167
+ // fall through
168
+ }
169
+ // Minimal inline fallback (green circle) — matches the brand tone but
170
+ // ships zero external dependencies.
171
+ const fallback = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">' +
172
+ '<circle cx="32" cy="32" r="30" fill="#171717"/>' +
173
+ '<circle cx="32" cy="32" r="22" fill="#3de678"/></svg>';
174
+ return `data:image/svg+xml;base64,${Buffer.from(fallback).toString('base64')}`;
175
+ }
176
+ /** Get development mode HTML */
177
+ getDevModeHTML() {
178
+ const modeText = this.config.standalone
179
+ ? '🔧 Standalone Mode - Agent running here'
180
+ : this.agentConnected
181
+ ? '✅ Connected to your app\'s agent'
182
+ : '⏳ Waiting for agent connection...';
183
+ const modeColor = this.config.standalone
184
+ ? '#f59e0b'
185
+ : this.agentConnected
186
+ ? '#22c55e'
187
+ : '#6b7280';
188
+ const iconDataUri = this.getIconDataUri();
189
+ return `
190
+ <!DOCTYPE html>
191
+ <html lang="en">
192
+ <head>
193
+ <meta charset="UTF-8">
194
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
195
+ <link rel="icon" type="image/svg+xml" href="${iconDataUri}">
196
+ <title>ExpressoTS Studio</title>
197
+ <style>
198
+ * { margin: 0; padding: 0; box-sizing: border-box; }
199
+ body {
200
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
201
+ background: #171717;
202
+ color: #fff;
203
+ min-height: 100vh;
204
+ display: flex;
205
+ align-items: center;
206
+ justify-content: center;
207
+ }
208
+ .container { text-align: center; padding: 40px; max-width: 600px; }
209
+ .logo {
210
+ width: 96px;
211
+ height: 96px;
212
+ margin: 0 auto 20px;
213
+ display: block;
214
+ border-radius: 50%;
215
+ box-shadow: 0 8px 32px rgba(61, 230, 120, 0.2);
216
+ }
217
+ h1 {
218
+ font-size: 2.5rem;
219
+ margin-bottom: 10px;
220
+ background: linear-gradient(90deg, #3de678, #19ce59);
221
+ -webkit-background-clip: text;
222
+ -webkit-text-fill-color: transparent;
223
+ background-clip: text;
224
+ }
225
+ .subtitle { color: #9ca3af; font-size: 1.1rem; margin-bottom: 30px; }
226
+ .status {
227
+ background: rgba(61, 230, 120, 0.08);
228
+ border: 1px solid ${modeColor}40;
229
+ border-radius: 12px;
230
+ padding: 20px 30px;
231
+ margin-bottom: 30px;
232
+ }
233
+ .status h2 { color: ${modeColor}; font-size: 1.2rem; margin-bottom: 10px; }
234
+ .status p { color: #9ca3af; }
235
+ .agent-info {
236
+ background: rgba(34, 197, 94, 0.1);
237
+ border: 1px solid rgba(34, 197, 94, 0.3);
238
+ border-radius: 8px;
239
+ padding: 15px;
240
+ margin-top: 20px;
241
+ }
242
+ .agent-info code { color: #22c55e; font-family: 'JetBrains Mono', monospace; }
243
+ .instructions { color: #6b7280; font-size: 0.9rem; margin-top: 30px; line-height: 1.8; }
244
+ .instructions code {
245
+ background: rgba(255,255,255,0.1);
246
+ padding: 2px 8px;
247
+ border-radius: 4px;
248
+ font-family: 'JetBrains Mono', monospace;
249
+ }
250
+ .help-box {
251
+ background: rgba(99, 102, 241, 0.1);
252
+ border: 1px solid rgba(99, 102, 241, 0.3);
253
+ border-radius: 8px;
254
+ padding: 20px;
255
+ margin-top: 30px;
256
+ text-align: left;
257
+ }
258
+ .help-box h3 { color: #818cf8; margin-bottom: 10px; }
259
+ .help-box ol { padding-left: 20px; color: #9ca3af; }
260
+ .help-box li { margin-bottom: 8px; }
261
+ </style>
262
+ </head>
263
+ <body>
264
+ <div class="container">
265
+ <img class="logo" src="${iconDataUri}" alt="ExpressoTS" />
266
+ <h1>ExpressoTS Studio</h1>
267
+ <p class="subtitle">Developer Experience Platform</p>
268
+
269
+ <div class="status">
270
+ <h2>${modeText}</h2>
271
+ <div class="agent-info">
272
+ <code>ws://localhost:${this.config.agentPort}</code>
273
+ </div>
274
+ </div>
275
+
276
+ ${!this.agentConnected && !this.config.standalone ? `
277
+ <div class="help-box">
278
+ <h3>📋 How to capture requests:</h3>
279
+ <ol>
280
+ <li>Install the agent in your app: <code>npm install @expressots/studio-agent</code></li>
281
+ <li>Restart your ExpressoTS app</li>
282
+ <li>Refresh this page</li>
283
+ </ol>
284
+ </div>
285
+ ` : ''}
286
+
287
+ <p class="instructions">
288
+ The Studio UI failed to load. Try reinstalling @expressots/studio.
289
+ </p>
290
+ </div>
291
+ </body>
292
+ </html>
293
+ `;
294
+ }
295
+ /** Check if agent is connected */
296
+ isAgentConnected() {
297
+ return this.agentConnected || this.agent !== null;
298
+ }
299
+ /** Get the Studio Agent instance (standalone mode only) */
300
+ getAgent() {
301
+ return this.agent;
302
+ }
303
+ /** Get configuration */
304
+ getConfig() {
305
+ return { ...this.config };
306
+ }
307
+ }
308
+ //# sourceMappingURL=studio.js.map