circuit-mcp 2.0.1 → 2.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/README.md CHANGED
@@ -1,10 +1,18 @@
1
- # Circuit MCP
1
+ # circuit-mcp
2
2
 
3
- Connect [Circuit](https://withcircuit.com) to Cursor and Claude Code via MCP (Model Context Protocol).
3
+ Customer feedback in. Engineering specs out. Solve it with your AI coding tool.
4
4
 
5
- Circuit turns customer feedback into engineering specs. The MCP puts your priorities and briefs directly in your AI coding assistant — so you build what matters without switching tabs.
5
+ Connect [Circuit](https://withcircuit.com) to **Cursor** and **Claude Code** via the Model Context Protocol.
6
6
 
7
- ## Quick Start
7
+ ---
8
+
9
+ ## Setup
10
+
11
+ ### Claude Code
12
+
13
+ ```bash
14
+ claude mcp add circuit -- npx circuit-mcp
15
+ ```
8
16
 
9
17
  ### Cursor
10
18
 
@@ -21,166 +29,148 @@ Add to `~/.cursor/mcp.json`:
21
29
  }
22
30
  ```
23
31
 
24
- ### Claude Code
25
-
26
- ```bash
27
- claude mcp add circuit -- npx circuit-mcp
28
- ```
29
-
30
- ### First Run
31
-
32
- On first use, Circuit opens your browser to authenticate. Your token is cached at `~/.circuit/token.json`.
33
-
34
- ```
35
- Circuit MCP
36
-
37
- First time setup — let's connect your account.
38
- Opening browser to authenticate...
39
-
40
- ✓ Connected!
41
- ```
32
+ First run opens your browser to authenticate. Token is cached at `~/.circuit/token.json` (30-day expiry).
42
33
 
43
34
  ---
44
35
 
45
- ## Tools
36
+ ## 4 Tools
46
37
 
47
- Four tools. Everything you need to go from "what should I build?" to "shipped."
38
+ ### `circuit.priorities`
48
39
 
49
- ### `circuit.priorities` — What should I work on?
40
+ What should I work on? Ranked customer priorities with trends, confidence, and pattern matching.
50
41
 
51
- Returns ranked priorities with trend data, confidence scores, and pattern matching from your shipping history.
42
+ | Parameter | Type | Description |
43
+ |-----------|------|-------------|
44
+ | `weekly` | boolean | Set to `true` to get the weekly digest instead (movements, new entries, drops, spikes) |
45
+ | `lens` | string | How to rank: `volume`, `urgency`, `revenue`, `retention`, `delight`, `feature` |
46
+ | `segment` | string | Customer segment: `enterprise`, `smb`, `all` |
47
+ | `limit` | number | Number of results (default: 5, max: 20) |
48
+ | `category` | string | Filter: `Bug`, `Feature`, `Friction`, `Complaint`, `Praise` |
52
49
 
53
- | Parameter | Description |
54
- |-----------|-------------|
55
- | `lens` | How to rank: `volume`, `urgency`, `revenue`, `retention`, `delight`, `feature` |
56
- | `segment` | Filter by customer segment: `enterprise`, `smb`, `all` |
57
- | `limit` | Number of priorities to return (default: 5, max: 20) |
58
- | `category` | Filter: `Bug`, `Feature`, `Friction`, `Complaint`, `Praise` |
50
+ **Also returns:** Session context (last ship, in-progress builds, suggested next action) and instinct confidence (pattern matching from your shipping history).
59
51
 
60
- Each priority includes a `matches_pattern` flag Circuit learns what you tend to ship and surfaces priorities that fit your instincts.
52
+ **Weekly mode** (`weekly: true`): Returns priority movements (rank changes), new entries, dropped items, and volume spikes compared to last week.
61
53
 
62
- **Try it:** *"What are my top priorities by revenue impact for enterprise customers?"*
54
+ ### `circuit.spec`
63
55
 
64
- ### `circuit.brief` The full engineering spec
56
+ Full engineering spec for a priority. 5 sections: What to Build, Why It Matters, Customer Voice (verbatim quotes), Files to Touch, Done When.
65
57
 
66
- Returns the complete brief for a priority: **What** to build, **Why** it matters, **Voice of the customer**, **Files** to touch, and **Done** criteria. Includes customer quotes, version history, and context from previous ships.
58
+ | Parameter | Type | Description |
59
+ |-----------|------|-------------|
60
+ | `priority_id` | string | Priority ID from `circuit.priorities` |
61
+ | `spec_id` | string | Spec ID directly (alternative to priority_id) |
62
+ | `include_history` | boolean | Include version history and ship memory (default: true) |
63
+ | `batch` | boolean | Set to `true` to export multiple specs as markdown |
64
+ | `spec_ids` | string[] | Specific spec IDs to export (batch mode) |
65
+ | `status` | string | Filter specs by status in batch mode: `ready`, `building`, `shipped` |
66
+ | `limit` | number | Number of specs in batch mode (default: 10, max: 50) |
67
67
 
68
- | Parameter | Description |
69
- |-----------|-------------|
70
- | `priority_id` or `build_id` | Which priority to get the brief for |
71
- | `include_history` | Include version history and related ship memory (default: true) |
68
+ **Also returns:** Post-ship signal (new feedback since shipping), effort estimate, codebase context (tech stack, related PRs, AI config files like CLAUDE.md), and related memories from previous ships.
72
69
 
73
- Uses a 3-layer matching strategy to find the right brief, even with partial or ambiguous IDs.
70
+ **Batch mode** (`batch: true`): Exports multiple specs as formatted markdown for sprint planning or documentation.
74
71
 
75
- **Try it:** *"Get the brief for priority #1"*
72
+ ### `circuit.act`
76
73
 
77
- ### `circuit.act` Take action
78
-
79
- One tool, four actions. Move work forward without leaving your editor.
74
+ Take action. Start building, ship it, share back, assign, correct, submit feedback, or submit a transcript.
80
75
 
81
76
  | Action | What it does |
82
77
  |--------|-------------|
83
- | `build` | Mark a brief as "building" — you're working on it |
84
- | `ship` | Mark shipped. Auto-notifies customers who submitted feedback via the widget. Creates a ship memory so Circuit learns your patterns. |
85
- | `correct` | Fix an AI classification that got it wrong. Writes a correction memory and recomputes pattern matching. |
86
- | `submit` | Add new feedback directly via MCP |
87
-
88
- **Try it:** *"Mark the login brief as building"* build it → *"Ship it"*
78
+ | `build` | Mark spec as "building" |
79
+ | `ship` | Mark as shipped, record memory |
80
+ | `share` | Notify customers who submitted feedback (email, widget, or both) |
81
+ | `assign` | Assign spec to a team member by email or user ID |
82
+ | `correct` | Fix a priority's category classification (Circuit remembers corrections) |
83
+ | `submit` | Add new feedback from your terminal |
84
+ | `transcript` | Submit a customer interview, sales call, or support transcript |
89
85
 
90
- ### `circuit.ask` Search across everything
86
+ **Share parameters** (when `action: "share"`):
91
87
 
92
- Semantic search across feedback, priorities, briefs, and help docs. Also returns your behavioral patterns — ship count, top categories, segment affinity — so your assistant has full context.
88
+ | Parameter | Type | Description |
89
+ |-----------|------|-------------|
90
+ | `spec_id` | string | Spec ID (required) |
91
+ | `channel` | string | `email`, `widget`, `both`, `skip` |
92
+ | `message` | string | Custom message to include in notifications (optional) |
93
93
 
94
- **Try it:** *"Search for feedback about onboarding friction"*
94
+ **Transcript parameters** (when `action: "transcript"`):
95
95
 
96
- ---
96
+ | Parameter | Type | Description |
97
+ |-----------|------|-------------|
98
+ | `text` | string | Full transcript text (min 50 chars, required) |
99
+ | `title` | string | Title, e.g. "Sales call with Acme Corp" |
100
+ | `type` | string | `interview`, `sales_call`, `support`, `other` |
101
+ | `customer_name` | string | Customer name (optional) |
102
+ | `customer_email` | string | Customer email (optional, for close-the-loop notifications) |
103
+ | `revenue_band` | string | `enterprise`, `paid`, `free` |
97
104
 
98
- ## Example Workflow
105
+ ### `circuit.ask`
99
106
 
100
- A typical session looks like this:
101
-
102
- ```
103
- You: "What should I work on?"
104
- Circuit: → circuit.priorities (lens: urgency)
105
- Returns top 5, #1 is "Login timeout on slow connections"
107
+ Semantic search across all your data: feedback, priorities, specs, and help articles. Returns your shipping patterns and behavioral insights. Also searches your connected GitHub repo for code context.
106
108
 
107
- You: "Get me the brief for that"
108
- Circuit: → circuit.brief (priority_id: ...)
109
- Returns spec: what to build, affected files, done criteria
109
+ | Parameter | Type | Description |
110
+ |-----------|------|-------------|
111
+ | `question` | string | Natural language, e.g. "What are enterprise customers saying about onboarding?" |
110
112
 
111
- You: "I'm on it"
112
- Circuit: → circuit.act (action: build)
113
- Marked as building
113
+ ---
114
114
 
115
- ... you write the code ...
115
+ ## Workflow
116
116
 
117
- You: "Done, ship it"
118
- Circuit: → circuit.act (action: ship)
119
- Marked as shipped. 12 customers notified.
120
117
  ```
121
-
122
- Feedback in. Spec out. Code shipped. Customers notified. That's the circuit.
118
+ > circuit.priorities {weekly: true} # What changed since last week?
119
+ > circuit.priorities # See what matters
120
+ > circuit.spec {priority_id: "..."} # Get the spec
121
+ > circuit.act {action: "build", ...} # Start building
122
+ > ... write the code ...
123
+ > circuit.act {action: "ship", ...} # Ship it.
124
+ > circuit.act {action: "share", channel: "both"} # Share back. Customers notified.
125
+ > circuit.spec {batch: true, status: "shipped"} # Export shipped specs
126
+ ```
123
127
 
124
128
  ---
125
129
 
126
- ## Commands
130
+ ## CLI Commands
127
131
 
128
132
  ```bash
129
- npx circuit-mcp # Start MCP server (used by Cursor/Claude Code)
130
- npx circuit-mcp setup # Show setup instructions
131
- npx circuit-mcp auth # Re-authenticate
132
- npx circuit-mcp logout # Clear stored token
133
+ npx circuit-mcp # Start MCP server (stdio mode)
134
+ npx circuit-mcp setup # Interactive setup
135
+ npx circuit-mcp auth # Re-authenticate
136
+ npx circuit-mcp logout # Clear stored token
137
+ npx circuit-mcp fix # Troubleshooting guide
133
138
  ```
134
139
 
135
- ## Troubleshooting
140
+ ---
136
141
 
137
- **"Authentication failed" or token expired**
138
- ```bash
139
- npx circuit-mcp auth
140
- ```
141
- Tokens last 30 days. Re-auth opens your browser — takes 5 seconds.
142
+ ## Troubleshooting
142
143
 
143
- **Cursor not detecting the MCP server**
144
- 1. Confirm `~/.cursor/mcp.json` has the config above
145
- 2. Restart Cursor
146
- 3. Check the MCP panel shows "circuit" as connected
144
+ **"Token expired"** Run `npx circuit-mcp auth` to re-authenticate.
147
145
 
148
- **Claude Code not finding tools**
149
- ```bash
150
- claude mcp list # Verify circuit is registered
151
- claude mcp remove circuit # Remove and re-add if needed
152
- claude mcp add circuit -- npx circuit-mcp
153
- ```
146
+ **"Connection refused"** Check your internet connection. The MCP connects to Circuit's API.
154
147
 
155
- **"Priority not found" errors**
148
+ **Tool not showing in Cursor/Claude** — Restart your editor after adding the MCP config.
156
149
 
157
- Circuit uses a 3-layer matching strategy, but if you're referencing old IDs, priorities may have been recomputed. Run `circuit.priorities` to get current IDs.
150
+ **Custom API URL** Set `CIRCUIT_API_URL` environment variable to override the default endpoint.
158
151
 
159
152
  ---
160
153
 
161
- ## How It Works
154
+ ## How it works
162
155
 
163
156
  ```
164
- Customer feedback
165
-
166
- Circuit (app.withcircuit.com)
167
- Feedback → Priorities → Specs
168
-
169
- Circuit MCP ←── Your AI assistant (Cursor / Claude Code)
170
-
171
- circuit.priorities → What to build
172
- circuit.brief → How to build it
173
- circuit.act → Ship it + notify customers
174
- circuit.ask → Search everything
157
+ Your editor (Cursor / Claude Code)
158
+ |
159
+ | JSON-RPC over stdio
160
+ |
161
+ circuit-mcp (this package)
162
+ |
163
+ | HTTPS + Bearer token
164
+ |
165
+ Circuit API
166
+ |
167
+ | PostgreSQL + pgvector
168
+ |
169
+ Your feedback data
175
170
  ```
176
171
 
177
- Auth uses OAuth 2.0 with PKCE. Tokens are SHA-256 hashed with 30-day expiry. All tools are memory-aware Circuit learns from your corrections and shipping patterns to get smarter over time.
178
-
179
- ## Links
172
+ The MCP server runs as a local process, communicating with your editor via stdin/stdout. All data stays in your Circuit account, authenticated via OAuth 2.0.
180
173
 
181
- - [Circuit](https://withcircuit.com) — Customer feedback intelligence
182
- - [MCP Protocol](https://modelcontextprotocol.io) — Model Context Protocol spec
183
-
184
- ## License
174
+ ---
185
175
 
186
- Proprietary © Circuit ([withcircuit.com](https://withcircuit.com))
176
+ Built by [Circuit](https://withcircuit.com). Feedback in. Specs out.
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circuit-mcp",
3
- "version": "2.0.1",
3
+ "version": "2.3.1",
4
4
  "description": "Connect Circuit to Cursor and Claude Code - bring customer priorities and engineering briefs into your AI coding assistant",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,57 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Circuit - Connection Failed</title>
5
+ <style>
6
+ * { margin: 0; padding: 0; box-sizing: border-box; }
7
+ body {
8
+ font-family: 'Geist', -apple-system, BlinkMacSystemFont, system-ui, 'Segoe UI', Roboto, sans-serif;
9
+ background: #F5F3F0;
10
+ min-height: 100vh;
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+ color: #1C1A18;
15
+ padding: 24px;
16
+ }
17
+ .content {
18
+ text-align: center;
19
+ max-width: 400px;
20
+ }
21
+ .icon {
22
+ margin-bottom: 24px;
23
+ }
24
+ .icon svg {
25
+ width: 48px;
26
+ height: 48px;
27
+ color: #D64545;
28
+ }
29
+ h1 {
30
+ font-size: 24px;
31
+ font-weight: 600;
32
+ color: #1C1A18;
33
+ margin-bottom: 12px;
34
+ }
35
+ p {
36
+ font-size: 14px;
37
+ color: rgba(28, 26, 24, 0.6);
38
+ line-height: 1.6;
39
+ }
40
+ .hint {
41
+ font-size: 12px;
42
+ color: rgba(28, 26, 24, 0.6);
43
+ margin-top: 16px;
44
+ }
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <div class="content">
49
+ <div class="icon">
50
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
51
+ </div>
52
+ <h1>Connection Failed</h1>
53
+ <p>Authentication timed out</p>
54
+ <p class="hint">Please try connecting again from your AI assistant.</p>
55
+ </div>
56
+ </body>
57
+ </html>
@@ -0,0 +1,57 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Circuit - Connected</title>
5
+ <style>
6
+ * { margin: 0; padding: 0; box-sizing: border-box; }
7
+ body {
8
+ font-family: 'Geist', -apple-system, BlinkMacSystemFont, system-ui, 'Segoe UI', Roboto, sans-serif;
9
+ background: #F5F3F0;
10
+ min-height: 100vh;
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+ color: #1C1A18;
15
+ padding: 24px;
16
+ }
17
+ .content {
18
+ text-align: center;
19
+ max-width: 400px;
20
+ }
21
+ .icon {
22
+ margin-bottom: 24px;
23
+ }
24
+ .icon svg {
25
+ width: 48px;
26
+ height: 48px;
27
+ color: #1C1A18;
28
+ }
29
+ h1 {
30
+ font-size: 24px;
31
+ font-weight: 600;
32
+ color: #1C1A18;
33
+ margin-bottom: 12px;
34
+ }
35
+ p {
36
+ font-size: 14px;
37
+ color: rgba(28, 26, 24, 0.6);
38
+ line-height: 1.6;
39
+ }
40
+ .hint {
41
+ font-size: 12px;
42
+ color: rgba(28, 26, 24, 0.6);
43
+ margin-top: 16px;
44
+ }
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <div class="content">
49
+ <div class="icon">
50
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
51
+ </div>
52
+ <h1>Connected.</h1>
53
+ <p>Your AI coding assistant is now connected to Circuit.</p>
54
+ <p class="hint">This window will close automatically...</p>
55
+ </div>
56
+ </body>
57
+ </html>
package/src/auth.js CHANGED
@@ -8,7 +8,7 @@ import open from 'open';
8
8
  import chalk from 'chalk';
9
9
  import { showSpinner, showInfo, showPrompt } from './ui.js';
10
10
 
11
- const CIRCUIT_URL = 'https://app.withcircuit.com';
11
+ const CIRCUIT_URL = process.env.CIRCUIT_APP_URL || 'https://app.withcircuit.com';
12
12
  const TOKEN_FILE = path.join(os.homedir(), '.circuit', 'token.json');
13
13
 
14
14
  /**
@@ -136,79 +136,57 @@ function getSuccessPage() {
136
136
  return `<!DOCTYPE html>
137
137
  <html>
138
138
  <head>
139
- <title>Circuit - Connected!</title>
139
+ <title>Circuit - Connected</title>
140
140
  <style>
141
141
  * { margin: 0; padding: 0; box-sizing: border-box; }
142
142
  body {
143
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
144
- background: #F5F1EA;
143
+ font-family: 'Geist', -apple-system, BlinkMacSystemFont, system-ui, 'Segoe UI', Roboto, sans-serif;
144
+ background: #F5F3F0;
145
145
  min-height: 100vh;
146
146
  display: flex;
147
147
  align-items: center;
148
148
  justify-content: center;
149
149
  color: #1C1A18;
150
+ padding: 24px;
150
151
  }
151
- .card {
152
- background: white;
153
- border-radius: 16px;
154
- box-shadow: 0 4px 24px rgba(0,0,0,0.08);
155
- padding: 48px 64px;
152
+ .content {
156
153
  text-align: center;
157
- max-width: 420px;
154
+ max-width: 400px;
158
155
  }
159
- .logo {
160
- display: flex;
161
- align-items: center;
162
- justify-content: center;
163
- gap: 8px;
164
- margin-bottom: 32px;
165
- }
166
- .logo-icon {
167
- width: 28px;
168
- height: 28px;
169
- background: #1C1A18;
170
- border-radius: 50%;
171
- display: flex;
172
- align-items: center;
173
- justify-content: center;
174
- }
175
- .logo-icon::after {
176
- content: '';
177
- width: 12px;
178
- height: 12px;
179
- border: 2px solid white;
180
- border-radius: 50%;
156
+ .icon {
157
+ margin-bottom: 24px;
181
158
  }
182
- .logo-text {
183
- font-size: 16px;
184
- font-weight: 500;
185
- }
186
- .woo {
187
- font-size: 14px;
188
- color: rgba(28,26,24,0.5);
189
- margin-bottom: 8px;
159
+ .icon svg {
160
+ width: 48px;
161
+ height: 48px;
162
+ color: #1C1A18;
190
163
  }
191
164
  h1 {
192
- font-size: 28px;
165
+ font-size: 24px;
193
166
  font-weight: 600;
167
+ color: #1C1A18;
194
168
  margin-bottom: 12px;
195
169
  }
196
170
  p {
197
- font-size: 15px;
198
- color: rgba(28,26,24,0.6);
199
- line-height: 1.5;
171
+ font-size: 14px;
172
+ color: rgba(28, 26, 24, 0.6);
173
+ line-height: 1.6;
174
+ }
175
+ .hint {
176
+ font-size: 12px;
177
+ color: rgba(28, 26, 24, 0.6);
178
+ margin-top: 16px;
200
179
  }
201
180
  </style>
202
181
  </head>
203
182
  <body>
204
- <div class="card">
205
- <div class="logo">
206
- <div class="logo-icon"></div>
207
- <span class="logo-text">Circuit</span>
183
+ <div class="content">
184
+ <div class="icon">
185
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
208
186
  </div>
209
- <p class="woo">Woo hoo!</p>
210
- <h1>Connected to Circuit</h1>
211
- <p>You can close this window to continue Circuiting.</p>
187
+ <h1>Connected.</h1>
188
+ <p>Your AI coding assistant is now connected to Circuit.</p>
189
+ <p class="hint">This window will close automatically...</p>
212
190
  </div>
213
191
  <script>setTimeout(() => window.close(), 3000);</script>
214
192
  </body>
@@ -216,32 +194,65 @@ function getSuccessPage() {
216
194
  }
217
195
 
218
196
  function getErrorPage(error) {
197
+ // Sanitize error message to prevent XSS in the static HTML page
198
+ const safeError = String(error).replace(/[<>&"']/g, c => ({
199
+ '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', "'": '&#39;'
200
+ })[c]);
201
+
219
202
  return `<!DOCTYPE html>
220
203
  <html>
221
204
  <head>
222
- <title>Circuit - Error</title>
205
+ <title>Circuit - Connection Failed</title>
223
206
  <style>
224
207
  * { margin: 0; padding: 0; box-sizing: border-box; }
225
208
  body {
226
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
227
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
209
+ font-family: 'Geist', -apple-system, BlinkMacSystemFont, system-ui, 'Segoe UI', Roboto, sans-serif;
210
+ background: #F5F3F0;
228
211
  min-height: 100vh;
229
212
  display: flex;
230
213
  align-items: center;
231
214
  justify-content: center;
232
- color: white;
215
+ color: #1C1A18;
216
+ padding: 24px;
217
+ }
218
+ .content {
219
+ text-align: center;
220
+ max-width: 400px;
221
+ }
222
+ .icon {
223
+ margin-bottom: 24px;
224
+ }
225
+ .icon svg {
226
+ width: 48px;
227
+ height: 48px;
228
+ color: #D64545;
229
+ }
230
+ h1 {
231
+ font-size: 24px;
232
+ font-weight: 600;
233
+ color: #1C1A18;
234
+ margin-bottom: 12px;
235
+ }
236
+ p {
237
+ font-size: 14px;
238
+ color: rgba(28, 26, 24, 0.6);
239
+ line-height: 1.6;
240
+ }
241
+ .hint {
242
+ font-size: 12px;
243
+ color: rgba(28, 26, 24, 0.6);
244
+ margin-top: 16px;
233
245
  }
234
- .container { text-align: center; padding: 48px; }
235
- .icon { font-size: 64px; margin-bottom: 24px; }
236
- h1 { font-size: 32px; color: #ef4444; margin-bottom: 16px; }
237
- p { font-size: 18px; color: rgba(255,255,255,0.7); }
238
246
  </style>
239
247
  </head>
240
248
  <body>
241
- <div class="container">
242
- <div class="icon">⚠️</div>
243
- <h1>Authentication Failed</h1>
244
- <p>${error}</p>
249
+ <div class="content">
250
+ <div class="icon">
251
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
252
+ </div>
253
+ <h1>Connection Failed</h1>
254
+ <p>${safeError}</p>
255
+ <p class="hint">Please try connecting again from your AI assistant.</p>
245
256
  </div>
246
257
  </body>
247
258
  </html>`;