qeek-mcp-beta 1.0.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 +128 -0
- package/package.json +23 -0
- package/src/cli.js +98 -0
- package/src/config.js +40 -0
- package/src/server.js +169 -0
- package/src/tools/getBriefContext.js +126 -0
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Qeek MCP
|
|
2
|
+
|
|
3
|
+
π **Access your Qeek briefs directly in Cursor and Windsurf**
|
|
4
|
+
|
|
5
|
+
Say "work on brief id: xxx" and instantly load all your product specs, technical specs, UI mockups, and diagrams into your AI assistant.
|
|
6
|
+
|
|
7
|
+
## π Quick Start
|
|
8
|
+
|
|
9
|
+
### 1. Install & Setup
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx qeek-mcp setup
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This will:
|
|
16
|
+
- Prompt you for your Qeek API token
|
|
17
|
+
- Save configuration to `~/.qeek/config.json`
|
|
18
|
+
- Auto-configure Cursor (`~/.cursor/mcp.json`)
|
|
19
|
+
- Auto-configure Windsurf (`~/.codeium/windsurf/mcp_config.json`)
|
|
20
|
+
|
|
21
|
+
### 2. Get Your Token
|
|
22
|
+
|
|
23
|
+
1. Go to https://app.qeek.ai/settings/api-tokens
|
|
24
|
+
2. Generate a new token (starts with `qek_live_`)
|
|
25
|
+
3. Paste it during setup
|
|
26
|
+
|
|
27
|
+
### 3. Use It
|
|
28
|
+
|
|
29
|
+
In Cursor or Windsurf, simply type:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
work on brief id: architect-1234567890-abcdef
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The MCP tool will automatically load all specs from that brief as context.
|
|
36
|
+
|
|
37
|
+
## π― How It Works
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
βββββββββββββββββββ ββββββββββββββββ βββββββββββββββββββ
|
|
41
|
+
β Cursor IDE βββββββΊβ qeek-mcp βββββββΊβ api.qeek.ai β
|
|
42
|
+
β or Windsurf βstdio β (npx) βHTTPS β /internal/briefs β
|
|
43
|
+
βββββββββββββββββββ ββββββββββββββββ βββββββββββββββββββ
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## π Features
|
|
47
|
+
|
|
48
|
+
- **One-command context loading** β All specs from your brief
|
|
49
|
+
- **Multi-type support** β Product, technical, UI mockups, diagrams
|
|
50
|
+
- **Smart formatting** β Organized markdown with clear sections
|
|
51
|
+
- **IDE integration** β Works with Cursor and Windsurf
|
|
52
|
+
- **Token-based auth** β Simple, secure, no OAuth redirects
|
|
53
|
+
|
|
54
|
+
## π§ Manual Configuration
|
|
55
|
+
|
|
56
|
+
If automatic setup doesn't work, manually add to your MCP config:
|
|
57
|
+
|
|
58
|
+
### Cursor (`~/.cursor/mcp.json`)
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"qeek": {
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": ["-y", "qeek-mcp"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Windsurf (`~/.codeium/windsurf/mcp_config.json`)
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"mcpServers": {
|
|
76
|
+
"qeek": {
|
|
77
|
+
"command": "npx",
|
|
78
|
+
"args": ["-y", "qeek-mcp"]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## π Usage Examples
|
|
85
|
+
|
|
86
|
+
**Load a brief:**
|
|
87
|
+
```
|
|
88
|
+
work on brief id: architect-1234567890-abcdef
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**With additional context:**
|
|
92
|
+
```
|
|
93
|
+
work on brief id: architect-1234567890-abcdef, I need to implement the authentication flow
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## π Troubleshooting
|
|
97
|
+
|
|
98
|
+
### "Qeek MCP not configured"
|
|
99
|
+
|
|
100
|
+
Run setup again:
|
|
101
|
+
```bash
|
|
102
|
+
npx qeek-mcp setup
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### "Invalid API token"
|
|
106
|
+
|
|
107
|
+
1. Go to https://app.qeek.ai/settings/api-tokens
|
|
108
|
+
2. Generate a new token
|
|
109
|
+
3. Re-run setup
|
|
110
|
+
|
|
111
|
+
### "Brief not found"
|
|
112
|
+
|
|
113
|
+
- Verify the brief ID is correct
|
|
114
|
+
- Ensure you have access to that brief in the Qeek UI
|
|
115
|
+
|
|
116
|
+
## π Security
|
|
117
|
+
|
|
118
|
+
- Token stored only in `~/.qeek/config.json` on your machine
|
|
119
|
+
- Never transmitted except to official Qeek API
|
|
120
|
+
- Token can be revoked at any time in Qeek UI
|
|
121
|
+
|
|
122
|
+
## π License
|
|
123
|
+
|
|
124
|
+
MIT License - see LICENSE for details.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
**Built with β€οΈ by the Qeek team**
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "qeek-mcp-beta",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Qeek MCP server for Cursor and Windsurf - Access your briefs directly in your IDE",
|
|
5
|
+
"main": "src/server.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"qeek-mcp": "./src/server.js",
|
|
8
|
+
"qeek-mcp-setup": "./src/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/server.js",
|
|
12
|
+
"setup": "node src/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["mcp", "cursor", "windsurf", "qeek", "briefs", "specs"],
|
|
15
|
+
"author": "Qeek",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const readline = require('readline');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { saveConfig } = require('./config');
|
|
7
|
+
|
|
8
|
+
const rl = readline.createInterface({
|
|
9
|
+
input: process.stdin,
|
|
10
|
+
output: process.stdout
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
async function setup() {
|
|
14
|
+
console.log('\nπ Qeek MCP Setup');
|
|
15
|
+
console.log('==================\n');
|
|
16
|
+
console.log('1. Go to: https://app.qeek.ai/settings/api-tokens');
|
|
17
|
+
console.log('2. Generate a new token');
|
|
18
|
+
console.log('3. Paste it below:\n');
|
|
19
|
+
|
|
20
|
+
const token = await new Promise(resolve => {
|
|
21
|
+
rl.question('Token: ', resolve);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (!token.startsWith('qek_live_')) {
|
|
25
|
+
console.log('\nβ οΈ Warning: Token should start with "qek_live_"');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Save config
|
|
29
|
+
saveConfig({
|
|
30
|
+
version: 1,
|
|
31
|
+
token: token.trim(),
|
|
32
|
+
apiUrl: 'https://api.qeek.ai',
|
|
33
|
+
createdAt: new Date().toISOString()
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Configure Cursor
|
|
37
|
+
await configureCursor();
|
|
38
|
+
|
|
39
|
+
// Configure Windsurf
|
|
40
|
+
await configureWindsurf();
|
|
41
|
+
|
|
42
|
+
console.log('\nβ
Setup complete!');
|
|
43
|
+
console.log('Use: "work on brief id: <brief-id>" in Cursor/Windsurf\n');
|
|
44
|
+
|
|
45
|
+
rl.close();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function configureCursor() {
|
|
49
|
+
const cursorDir = path.join(os.homedir(), '.cursor');
|
|
50
|
+
const mcpConfigPath = path.join(cursorDir, 'mcp.json');
|
|
51
|
+
|
|
52
|
+
let config = {};
|
|
53
|
+
if (fs.existsSync(mcpConfigPath)) {
|
|
54
|
+
config = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
config.mcpServers = config.mcpServers || {};
|
|
58
|
+
config.mcpServers.qeek = {
|
|
59
|
+
command: 'npx',
|
|
60
|
+
args: ['-y', 'qeek-mcp']
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (!fs.existsSync(cursorDir)) {
|
|
64
|
+
fs.mkdirSync(cursorDir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fs.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2));
|
|
68
|
+
console.log('β Cursor MCP config updated');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function configureWindsurf() {
|
|
72
|
+
const windsurfDir = path.join(os.homedir(), '.codeium', 'windsurf');
|
|
73
|
+
const mcpConfigPath = path.join(windsurfDir, 'mcp_config.json');
|
|
74
|
+
|
|
75
|
+
let config = {};
|
|
76
|
+
if (fs.existsSync(mcpConfigPath)) {
|
|
77
|
+
config = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
config.mcpServers = config.mcpServers || {};
|
|
81
|
+
config.mcpServers.qeek = {
|
|
82
|
+
command: 'npx',
|
|
83
|
+
args: ['-y', 'qeek-mcp']
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (!fs.existsSync(windsurfDir)) {
|
|
87
|
+
fs.mkdirSync(windsurfDir, { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fs.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2));
|
|
91
|
+
console.log('β Windsurf MCP config updated');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (require.main === module) {
|
|
95
|
+
setup().catch(console.error);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { setup };
|
package/src/config.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), '.qeek');
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
7
|
+
|
|
8
|
+
function loadConfig() {
|
|
9
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
'Qeek MCP not configured. Run: npx qeek-mcp setup\n' +
|
|
12
|
+
'Get your token at: https://app.qeek.ai/settings/api-tokens'
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
token: config.token,
|
|
20
|
+
apiUrl: config.apiUrl || 'https://api.qeek.ai'
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function saveConfig(config) {
|
|
25
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
26
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
30
|
+
|
|
31
|
+
// Ensure permissions even if umask interfered
|
|
32
|
+
try {
|
|
33
|
+
fs.chmodSync(CONFIG_FILE, 0o600);
|
|
34
|
+
fs.chmodSync(CONFIG_DIR, 0o700);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// Ignore permission errors on Windows or restricted environments
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { loadConfig, saveConfig, CONFIG_DIR };
|
package/src/server.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Qeek MCP Server - Brief Context Tool
|
|
5
|
+
*
|
|
6
|
+
* This script creates an MCP-compatible server that provides the getBriefContext tool
|
|
7
|
+
* for loading Qeek brief specs into Cursor/Windsurf.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Handle setup command
|
|
11
|
+
if (process.argv[2] === 'setup') {
|
|
12
|
+
const { setup } = require('./cli.js');
|
|
13
|
+
setup().then(() => process.exit(0)).catch(err => {
|
|
14
|
+
console.error('Setup failed:', err);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
});
|
|
17
|
+
return; // Prevent falling through to server startup
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { loadConfig } = require('./config');
|
|
21
|
+
const { getBriefContext } = require('./tools/getBriefContext');
|
|
22
|
+
|
|
23
|
+
const config = loadConfig();
|
|
24
|
+
|
|
25
|
+
const tools = [
|
|
26
|
+
{
|
|
27
|
+
name: 'getBriefContext',
|
|
28
|
+
description: 'π Load all specs (product, tech, UI mockups, diagrams) from a Qeek brief. Use when user says "work on brief id: xxx" or "load brief xxx".',
|
|
29
|
+
inputSchema: {
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: {
|
|
32
|
+
briefId: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'The brief/chat session ID (e.g., "architect-1234567890-abcdef")'
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
required: ['briefId']
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
function sendResponse(response) {
|
|
43
|
+
console.log(JSON.stringify(response));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function handleRequest(request) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = typeof request === 'string' ? JSON.parse(request) : request;
|
|
49
|
+
|
|
50
|
+
// Handle notifications (no id field) - don't send response
|
|
51
|
+
if (parsed.id === undefined) {
|
|
52
|
+
// Log notifications for debugging but don't respond
|
|
53
|
+
if (parsed.method?.startsWith('notifications/')) {
|
|
54
|
+
console.error(`π¨ Notification: ${parsed.method}`);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
switch (parsed.method) {
|
|
60
|
+
case 'initialize':
|
|
61
|
+
sendResponse({
|
|
62
|
+
jsonrpc: '2.0',
|
|
63
|
+
id: parsed.id,
|
|
64
|
+
result: {
|
|
65
|
+
protocolVersion: '2024-11-05',
|
|
66
|
+
capabilities: {
|
|
67
|
+
tools: {}
|
|
68
|
+
},
|
|
69
|
+
serverInfo: {
|
|
70
|
+
name: 'qeek-mcp',
|
|
71
|
+
version: '1.0.0',
|
|
72
|
+
description: 'π Load Qeek brief specs into your IDE'
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case 'tools/list':
|
|
79
|
+
sendResponse({
|
|
80
|
+
jsonrpc: '2.0',
|
|
81
|
+
id: parsed.id,
|
|
82
|
+
result: { tools }
|
|
83
|
+
});
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case 'tools/call':
|
|
87
|
+
const { name, arguments: args } = parsed.params;
|
|
88
|
+
|
|
89
|
+
if (name === 'getBriefContext') {
|
|
90
|
+
const result = await getBriefContext(args.briefId, config);
|
|
91
|
+
sendResponse({
|
|
92
|
+
jsonrpc: '2.0',
|
|
93
|
+
id: parsed.id,
|
|
94
|
+
result
|
|
95
|
+
});
|
|
96
|
+
} else {
|
|
97
|
+
sendResponse({
|
|
98
|
+
jsonrpc: '2.0',
|
|
99
|
+
id: parsed.id,
|
|
100
|
+
error: {
|
|
101
|
+
code: -32601,
|
|
102
|
+
message: `Unknown tool: ${name}`
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
|
|
108
|
+
default:
|
|
109
|
+
sendResponse({
|
|
110
|
+
jsonrpc: '2.0',
|
|
111
|
+
id: parsed.id,
|
|
112
|
+
error: {
|
|
113
|
+
code: -32601,
|
|
114
|
+
message: `Unknown method: ${parsed.method}`
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
sendResponse({
|
|
120
|
+
jsonrpc: '2.0',
|
|
121
|
+
error: {
|
|
122
|
+
code: -32700,
|
|
123
|
+
message: 'Parse error',
|
|
124
|
+
data: error.message
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function start() {
|
|
131
|
+
console.error('π Qeek MCP Server starting...');
|
|
132
|
+
console.error('π Tool: getBriefContext');
|
|
133
|
+
console.error('π‘ MCP Server ready - listening for requests...');
|
|
134
|
+
|
|
135
|
+
process.stdin.setEncoding('utf8');
|
|
136
|
+
|
|
137
|
+
// Buffer for handling partial messages
|
|
138
|
+
let buffer = '';
|
|
139
|
+
|
|
140
|
+
process.stdin.on('data', (data) => {
|
|
141
|
+
buffer += data;
|
|
142
|
+
|
|
143
|
+
// Process complete lines from buffer
|
|
144
|
+
let newlineIndex;
|
|
145
|
+
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
|
|
146
|
+
const line = buffer.substring(0, newlineIndex).trim();
|
|
147
|
+
buffer = buffer.substring(newlineIndex + 1);
|
|
148
|
+
|
|
149
|
+
if (line) {
|
|
150
|
+
handleRequest(line);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
process.stdin.on('end', () => {
|
|
156
|
+
// Process any remaining data in buffer
|
|
157
|
+
const remaining = buffer.trim();
|
|
158
|
+
if (remaining) {
|
|
159
|
+
handleRequest(remaining);
|
|
160
|
+
}
|
|
161
|
+
process.exit(0);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
process.on('SIGINT', () => process.exit(0));
|
|
165
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
start();
|
|
169
|
+
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
async function getBriefContext(briefId, config) {
|
|
2
|
+
console.error(`π Loading brief: ${briefId}`);
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
const response = await fetch(`${config.apiUrl}/internal/briefs/${briefId}`, {
|
|
6
|
+
headers: {
|
|
7
|
+
'Authorization': `Bearer ${config.token}`,
|
|
8
|
+
'Content-Type': 'application/json'
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
if (response.status === 401) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{
|
|
15
|
+
type: 'text',
|
|
16
|
+
text: 'β Invalid API token.\n\nPlease run: `npx qeek-mcp setup`\nGet a new token at: https://app.qeek.ai/settings/api-tokens'
|
|
17
|
+
}],
|
|
18
|
+
isError: true
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (response.status === 404) {
|
|
23
|
+
return {
|
|
24
|
+
content: [{
|
|
25
|
+
type: 'text',
|
|
26
|
+
text: `β Brief not found: ${briefId}\n\nPlease verify the brief ID is correct.`
|
|
27
|
+
}],
|
|
28
|
+
isError: true
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error(`API error: ${response.status}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const brief = await response.json();
|
|
37
|
+
const markdown = formatBriefAsMarkdown(brief);
|
|
38
|
+
|
|
39
|
+
console.error(`β
Loaded brief with ${brief.specs?.length || 0} specs`);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
content: [{ type: 'text', text: markdown }],
|
|
43
|
+
structuredContent: {
|
|
44
|
+
briefId: brief.id,
|
|
45
|
+
title: brief.title,
|
|
46
|
+
projectId: brief.projectId,
|
|
47
|
+
repositoryNames: brief.repositoryNames,
|
|
48
|
+
specCounts: countSpecsByType(brief.specs)
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('β Error:', error.message);
|
|
54
|
+
return {
|
|
55
|
+
content: [{
|
|
56
|
+
type: 'text',
|
|
57
|
+
text: `β Error loading brief: ${error.message}`
|
|
58
|
+
}],
|
|
59
|
+
isError: true
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function formatBriefAsMarkdown(brief) {
|
|
65
|
+
let md = `# π Brief: ${brief.title}\n\n`;
|
|
66
|
+
md += `**ID:** \`${brief.id}\`\n`;
|
|
67
|
+
md += `**Project:** ${brief.projectId}\n`;
|
|
68
|
+
md += `**Repos:** ${brief.repositoryNames?.join(', ') || 'None'}\n\n`;
|
|
69
|
+
md += `---\n\n`;
|
|
70
|
+
|
|
71
|
+
// Group and format specs
|
|
72
|
+
const specs = brief.specs || [];
|
|
73
|
+
|
|
74
|
+
const productSpecs = specs.filter(s => s.specType === 'product');
|
|
75
|
+
const techSpecs = specs.filter(s => s.specType === 'tech');
|
|
76
|
+
const uiSpecs = specs.filter(s => s.specType === 'ui');
|
|
77
|
+
const diagramSpecs = specs.filter(s => s.specType === 'diagram');
|
|
78
|
+
const customSpecs = specs.filter(s => s.specType === 'custom');
|
|
79
|
+
|
|
80
|
+
if (productSpecs.length) {
|
|
81
|
+
md += `## π Product Specs\n\n`;
|
|
82
|
+
productSpecs.forEach(s => {
|
|
83
|
+
md += `### ${s.title}\n\n${s.content}\n\n---\n\n`;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (techSpecs.length) {
|
|
88
|
+
md += `## βοΈ Technical Specs\n\n`;
|
|
89
|
+
techSpecs.forEach(s => {
|
|
90
|
+
md += `### ${s.title}\n\n${s.content}\n\n---\n\n`;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (uiSpecs.length) {
|
|
95
|
+
md += `## π¨ UI Mockups\n\n`;
|
|
96
|
+
uiSpecs.forEach(s => {
|
|
97
|
+
md += `### ${s.title}\n\n${s.content}\n\n---\n\n`;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (diagramSpecs.length) {
|
|
102
|
+
md += `## π Diagrams\n\n`;
|
|
103
|
+
diagramSpecs.forEach(s => {
|
|
104
|
+
md += `### ${s.title}\n\n${s.content}\n\n---\n\n`;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (customSpecs.length) {
|
|
109
|
+
md += `## π Custom Specs\n\n`;
|
|
110
|
+
customSpecs.forEach(s => {
|
|
111
|
+
md += `### ${s.title}\n\n${s.content}\n\n---\n\n`;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return md;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function countSpecsByType(specs) {
|
|
119
|
+
const counts = { product: 0, tech: 0, ui: 0, diagram: 0, custom: 0 };
|
|
120
|
+
(specs || []).forEach(s => {
|
|
121
|
+
counts[s.specType] = (counts[s.specType] || 0) + 1;
|
|
122
|
+
});
|
|
123
|
+
return counts;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = { getBriefContext };
|