github-llm-council 0.0.1 → 2.0.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/LICENSE +1 -1
- package/README.md +46 -47
- package/bin/cli.js +96 -0
- package/dist/src/app.d.ts +2 -0
- package/dist/src/app.d.ts.map +1 -0
- package/dist/src/app.js +182 -0
- package/dist/src/app.js.map +1 -0
- package/dist/src/server.d.ts +2 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +8 -0
- package/dist/src/server.js.map +1 -0
- package/package.json +53 -67
- package/src/public/app.js +218 -0
- package/src/public/index.html +83 -0
- package/src/public/styles.css +351 -0
- package/bin/install.js +0 -45
- package/dist/llm-council.vsix +0 -0
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,63 +1,62 @@
|
|
|
1
|
-
# LLM Council
|
|
1
|
+
# LLM Council
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Watch multiple AI models debate side-by-side using GitHub Copilot.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- Command: `LLM Council` (editor context menu / `llmCouncil.run`).
|
|
7
|
-
- Flow: pick context (file/selection/none) → pick prompt template → edit prompt → pick models (defaults auto-resolved and remembered per workspace) → council runs (3 stages) with streaming output.
|
|
8
|
-
- Models: via `vscode.lm.selectChatModels`; defaults prefer `gpt-5.1`, `sonnet-4.5`, `gemini-pro-3` when available.
|
|
9
|
-
- Output: streams tagged by stage/model; a markdown artifact (with front matter + transcripts) is saved to the workspace root and opened in a new tab. History kept in global storage (`llmCouncil.historySize`).
|
|
10
|
-
- Tests: unit/integration via Vitest.
|
|
5
|
+
Type a spicy question, watch GPT-5, Claude, and Gemini respond simultaneously with a dark hacker aesthetic.
|
|
11
6
|
|
|
12
|
-
##
|
|
13
|
-
Prereqs: VS Code ≥1.84, VS Code CLI `code` on PATH, Copilot (or other LM API provider) with chat models enabled.
|
|
7
|
+
## Quick Start
|
|
14
8
|
|
|
15
|
-
Dev/local install
|
|
16
9
|
```bash
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
code --install-extension dist/llm-council.vsix --force # install into VS Code
|
|
20
|
-
# Optional: npm pack # to produce github-llm-council-0.0.1.tgz
|
|
21
|
-
```
|
|
10
|
+
# Prerequisites: Copilot CLI installed and authenticated
|
|
11
|
+
copilot auth login
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
npm install -g github-llm-council # postinstall auto-installs the bundled VSIX via `code`
|
|
13
|
+
# Run
|
|
14
|
+
npx github-llm-council
|
|
26
15
|
```
|
|
27
16
|
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npm update -g github-llm-council # or npm install -g github-llm-council@latest
|
|
31
|
-
```
|
|
17
|
+
Opens http://localhost:3000 - pick a prompt or type your own, watch the models respond in real-time.
|
|
32
18
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
- **Node.js 18+**
|
|
22
|
+
- **GitHub Copilot subscription** (Individual, Business, or Enterprise)
|
|
23
|
+
- **Copilot CLI** installed and authenticated:
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g @github/copilot-cli
|
|
26
|
+
copilot auth login
|
|
27
|
+
```
|
|
40
28
|
|
|
41
|
-
##
|
|
42
|
-
1) Right-click in an editor → `LLM Council` (or run via Command Palette).
|
|
43
|
-
2) Choose context (file/selection/none).
|
|
44
|
-
3) Pick a prompt template, then edit/enter your prompt.
|
|
45
|
-
4) Confirm model selection (defaults pre-selected and remembered per workspace).
|
|
46
|
-
5) Watch streaming output; a markdown artifact opens and saves to the workspace root.
|
|
29
|
+
## Available Models
|
|
47
30
|
|
|
48
|
-
|
|
31
|
+
The council can use any model available through GitHub Copilot:
|
|
49
32
|
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
33
|
+
- GPT-5, GPT-5.1, GPT-5.2
|
|
34
|
+
- Claude Opus 4.5, Claude Sonnet 4.5, Claude Sonnet 4
|
|
35
|
+
- Gemini 3 Pro
|
|
36
|
+
- And more...
|
|
37
|
+
|
|
38
|
+
## How It Works
|
|
39
|
+
|
|
40
|
+
Uses the [@github/copilot-sdk](https://github.com/github/copilot-sdk) to spawn parallel streaming sessions. Each model receives the same prompt and streams its response independently.
|
|
41
|
+
|
|
42
|
+
See `src/app.ts` for the Express server implementation.
|
|
43
|
+
|
|
44
|
+
## Development
|
|
53
45
|
|
|
54
|
-
## Testing
|
|
55
46
|
```bash
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
npm
|
|
47
|
+
git clone https://github.com/varunr89/github-llm-council
|
|
48
|
+
cd github-llm-council
|
|
49
|
+
npm install
|
|
50
|
+
npm run dev
|
|
59
51
|
```
|
|
60
52
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
-
|
|
53
|
+
### Scripts
|
|
54
|
+
|
|
55
|
+
- `npm run dev` - Start development server with hot reload
|
|
56
|
+
- `npm run build` - Compile TypeScript
|
|
57
|
+
- `npm run test` - Run unit tests
|
|
58
|
+
- `npm run test:e2e` - Run end-to-end tests
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn, execSync } from 'node:child_process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
// Check for GitHub Copilot CLI before starting
|
|
11
|
+
function checkCopilotCLI() {
|
|
12
|
+
try {
|
|
13
|
+
execSync('gh copilot --version', { stdio: 'pipe' });
|
|
14
|
+
return true;
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function checkGitHubCLI() {
|
|
21
|
+
try {
|
|
22
|
+
execSync('gh --version', { stdio: 'pipe' });
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!checkGitHubCLI()) {
|
|
30
|
+
console.error(`
|
|
31
|
+
╔════════════════════════════════════════════════════════════════════╗
|
|
32
|
+
║ ERROR: GitHub CLI (gh) is not installed ║
|
|
33
|
+
╠════════════════════════════════════════════════════════════════════╣
|
|
34
|
+
║ This tool requires the GitHub CLI to access Copilot models. ║
|
|
35
|
+
║ ║
|
|
36
|
+
║ Install it from: https://cli.github.com/ ║
|
|
37
|
+
║ ║
|
|
38
|
+
║ After installing, authenticate with: ║
|
|
39
|
+
║ gh auth login ║
|
|
40
|
+
╚════════════════════════════════════════════════════════════════════╝
|
|
41
|
+
`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!checkCopilotCLI()) {
|
|
46
|
+
console.error(`
|
|
47
|
+
╔════════════════════════════════════════════════════════════════════╗
|
|
48
|
+
║ ERROR: GitHub Copilot CLI extension is not installed ║
|
|
49
|
+
╠════════════════════════════════════════════════════════════════════╣
|
|
50
|
+
║ This tool requires the Copilot extension for the GitHub CLI. ║
|
|
51
|
+
║ ║
|
|
52
|
+
║ Install it with: ║
|
|
53
|
+
║ gh extension install github/gh-copilot ║
|
|
54
|
+
║ ║
|
|
55
|
+
║ You also need an active GitHub Copilot subscription. ║
|
|
56
|
+
║ Learn more: https://github.com/features/copilot ║
|
|
57
|
+
╚════════════════════════════════════════════════════════════════════╝
|
|
58
|
+
`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const port = process.env.PORT || '3000';
|
|
63
|
+
const noOpen = process.argv.includes('--no-open');
|
|
64
|
+
|
|
65
|
+
// Path to compiled server
|
|
66
|
+
const serverPath = path.join(__dirname, '..', 'dist', 'src', 'server.js');
|
|
67
|
+
|
|
68
|
+
console.log(`Starting LLM Council on port ${port}...`);
|
|
69
|
+
|
|
70
|
+
const server = spawn('node', [serverPath], {
|
|
71
|
+
env: { ...process.env, PORT: port },
|
|
72
|
+
stdio: 'inherit',
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!noOpen) {
|
|
76
|
+
const open = await import('open');
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
const url = `http://localhost:${port}`;
|
|
79
|
+
console.log(`Opening ${url} in browser...`);
|
|
80
|
+
open.default(url);
|
|
81
|
+
}, 2000);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
process.on('SIGINT', () => {
|
|
85
|
+
server.kill('SIGINT');
|
|
86
|
+
process.exit(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
process.on('SIGTERM', () => {
|
|
90
|
+
server.kill('SIGTERM');
|
|
91
|
+
process.exit(0);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
server.on('exit', (code) => {
|
|
95
|
+
process.exit(code ?? 0);
|
|
96
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/app.ts"],"names":[],"mappings":"AAgDA,wBAAsB,SAAS,yDAiK9B"}
|
package/dist/src/app.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { CopilotClient } from '@github/copilot-sdk';
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
// Resolve public path - works both in dev (src/) and prod (dist/src/)
|
|
9
|
+
function resolvePublicPath() {
|
|
10
|
+
// Check if sibling public folder exists (dev mode: src/public)
|
|
11
|
+
const siblingPublic = path.join(__dirname, 'public');
|
|
12
|
+
if (fs.existsSync(siblingPublic)) {
|
|
13
|
+
return siblingPublic;
|
|
14
|
+
}
|
|
15
|
+
// Fallback: assume we're in dist/src/, go up to find src/public
|
|
16
|
+
return path.join(__dirname, '..', '..', 'src', 'public');
|
|
17
|
+
}
|
|
18
|
+
// Available models from GitHub Copilot CLI
|
|
19
|
+
const AVAILABLE_MODELS = [
|
|
20
|
+
// GPT Models
|
|
21
|
+
{ id: 'gpt-5.2', name: 'GPT-5.2' },
|
|
22
|
+
{ id: 'gpt-5.2-codex', name: 'GPT-5.2 Codex' },
|
|
23
|
+
{ id: 'gpt-5.1', name: 'GPT-5.1' },
|
|
24
|
+
{ id: 'gpt-5.1-codex', name: 'GPT-5.1 Codex' },
|
|
25
|
+
{ id: 'gpt-5.1-codex-max', name: 'GPT-5.1 Codex Max' },
|
|
26
|
+
{ id: 'gpt-5.1-codex-mini', name: 'GPT-5.1 Codex Mini (Preview)' },
|
|
27
|
+
{ id: 'gpt-5', name: 'GPT-5' },
|
|
28
|
+
{ id: 'gpt-5-mini', name: 'GPT-5 Mini' },
|
|
29
|
+
{ id: 'gpt-5-codex', name: 'GPT-5 Codex (Preview)' },
|
|
30
|
+
{ id: 'gpt-4.1', name: 'GPT-4.1' },
|
|
31
|
+
{ id: 'gpt-4o', name: 'GPT-4o' },
|
|
32
|
+
// Claude Models
|
|
33
|
+
{ id: 'claude-opus-4.5', name: 'Claude Opus 4.5' },
|
|
34
|
+
{ id: 'claude-opus-4.1', name: 'Claude Opus 4.1' },
|
|
35
|
+
{ id: 'claude-sonnet-4.5', name: 'Claude Sonnet 4.5' },
|
|
36
|
+
{ id: 'claude-sonnet-4', name: 'Claude Sonnet 4' },
|
|
37
|
+
{ id: 'claude-haiku-4.5', name: 'Claude Haiku 4.5' },
|
|
38
|
+
// Gemini Models
|
|
39
|
+
{ id: 'gemini-3-pro-preview', name: 'Gemini 3 Pro (Preview)' },
|
|
40
|
+
{ id: 'gemini-3-flash-preview', name: 'Gemini 3 Flash (Preview)' },
|
|
41
|
+
{ id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro' },
|
|
42
|
+
// Internal
|
|
43
|
+
{ id: 'goldeneye', name: 'Goldeneye (Internal Only)' },
|
|
44
|
+
];
|
|
45
|
+
export async function createApp() {
|
|
46
|
+
const app = express();
|
|
47
|
+
app.use(express.json());
|
|
48
|
+
const MAX_MODELS = Number(process.env.MAX_MODELS ?? 3);
|
|
49
|
+
const useMock = process.env.COPILOT_MOCK === '1';
|
|
50
|
+
const client = useMock ? null : new CopilotClient();
|
|
51
|
+
if (client) {
|
|
52
|
+
await client.start();
|
|
53
|
+
}
|
|
54
|
+
// Serve static files from public directory
|
|
55
|
+
const publicPath = resolvePublicPath();
|
|
56
|
+
app.use(express.static(publicPath));
|
|
57
|
+
app.get('/api/models', (_req, res) => {
|
|
58
|
+
res.json({ models: AVAILABLE_MODELS });
|
|
59
|
+
});
|
|
60
|
+
app.post('/api/ask', async (req, res) => {
|
|
61
|
+
const prompt = typeof req.body?.prompt === 'string' ? req.body.prompt : '';
|
|
62
|
+
if (!prompt.trim()) {
|
|
63
|
+
res.status(400).json({ error: 'Prompt is required.' });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!client) {
|
|
67
|
+
res.json({ content: '[mock] response' });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const session = await client.createSession({ model: 'gpt-5' });
|
|
71
|
+
const message = await session.sendAndWait({ prompt });
|
|
72
|
+
await session.destroy();
|
|
73
|
+
res.json({ content: message?.data.content ?? '' });
|
|
74
|
+
});
|
|
75
|
+
app.post('/api/stream', async (req, res) => {
|
|
76
|
+
const prompt = typeof req.body?.prompt === 'string' ? req.body.prompt : '';
|
|
77
|
+
if (!prompt.trim()) {
|
|
78
|
+
res.status(400).json({ error: 'Prompt is required.' });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
82
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
83
|
+
res.setHeader('Connection', 'keep-alive');
|
|
84
|
+
res.flushHeaders();
|
|
85
|
+
if (!client) {
|
|
86
|
+
res.write(`data: ${JSON.stringify({ delta: '[mock] stream' })}\n\n`);
|
|
87
|
+
res.write('event: done\n');
|
|
88
|
+
res.write('data: {}\n\n');
|
|
89
|
+
res.end();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const session = await client.createSession({ model: 'gpt-5', streaming: true });
|
|
93
|
+
const unsubscribe = session.on((event) => {
|
|
94
|
+
if (event.type === 'assistant.message_delta') {
|
|
95
|
+
const chunk = event.data.deltaContent ?? '';
|
|
96
|
+
if (chunk) {
|
|
97
|
+
res.write(`data: ${JSON.stringify({ delta: chunk })}\n\n`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (event.type === 'assistant.message') {
|
|
101
|
+
res.write(`data: ${JSON.stringify({ content: event.data.content })}\n\n`);
|
|
102
|
+
}
|
|
103
|
+
else if (event.type === 'session.idle') {
|
|
104
|
+
res.write('event: done\n');
|
|
105
|
+
res.write('data: {}\n\n');
|
|
106
|
+
res.end();
|
|
107
|
+
unsubscribe();
|
|
108
|
+
session.destroy();
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
try {
|
|
112
|
+
await session.send({ prompt });
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
res.write(`data: ${JSON.stringify({ error: String(error) })}\n\n`);
|
|
116
|
+
res.end();
|
|
117
|
+
unsubscribe();
|
|
118
|
+
await session.destroy();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
app.post('/api/council', async (req, res) => {
|
|
122
|
+
const prompt = typeof req.body?.prompt === 'string' ? req.body.prompt : '';
|
|
123
|
+
const models = Array.isArray(req.body?.models) ? req.body.models : [];
|
|
124
|
+
if (!prompt.trim()) {
|
|
125
|
+
res.status(400).json({ error: 'Prompt is required.' });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (models.length === 0) {
|
|
129
|
+
res.status(400).json({ error: 'At least one model is required.' });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (models.length > MAX_MODELS) {
|
|
133
|
+
res.status(400).json({ error: `MAX_MODELS=${MAX_MODELS} exceeded.` });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
137
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
138
|
+
res.setHeader('Connection', 'keep-alive');
|
|
139
|
+
res.flushHeaders();
|
|
140
|
+
if (!client) {
|
|
141
|
+
models.forEach((modelId) => {
|
|
142
|
+
res.write(`data: ${JSON.stringify({ model: modelId, delta: '[mock] response' })}\n\n`);
|
|
143
|
+
res.write(`data: ${JSON.stringify({ model: modelId, done: true })}\n\n`);
|
|
144
|
+
});
|
|
145
|
+
res.write('event: done\ndata: {}\n\n');
|
|
146
|
+
res.end();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const sessions = await Promise.all(models.map((modelId) => client.createSession({ model: modelId, streaming: true })));
|
|
150
|
+
let completedCount = 0;
|
|
151
|
+
const unsubscribers = [];
|
|
152
|
+
sessions.forEach((session, index) => {
|
|
153
|
+
const modelId = models[index];
|
|
154
|
+
const unsubscribe = session.on((event) => {
|
|
155
|
+
if (event.type === 'assistant.message_delta') {
|
|
156
|
+
const chunk = event.data?.deltaContent ?? '';
|
|
157
|
+
if (chunk) {
|
|
158
|
+
res.write(`data: ${JSON.stringify({ model: modelId, delta: chunk })}\n\n`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else if (event.type === 'session.idle') {
|
|
162
|
+
completedCount++;
|
|
163
|
+
res.write(`data: ${JSON.stringify({ model: modelId, done: true })}\n\n`);
|
|
164
|
+
if (completedCount === models.length) {
|
|
165
|
+
res.write('event: done\ndata: {}\n\n');
|
|
166
|
+
res.end();
|
|
167
|
+
unsubscribers.forEach((unsub) => unsub());
|
|
168
|
+
sessions.forEach((s) => s.destroy());
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
unsubscribers.push(unsubscribe);
|
|
173
|
+
});
|
|
174
|
+
sessions.forEach((session, index) => {
|
|
175
|
+
session.send({ prompt }).catch((error) => {
|
|
176
|
+
res.write(`data: ${JSON.stringify({ model: models[index], error: String(error) })}\n\n`);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
return app;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createApp } from './app.js';
|
|
2
|
+
const app = await createApp();
|
|
3
|
+
const port = Number(process.env.PORT ?? 3000);
|
|
4
|
+
const host = process.env.HOST ?? '0.0.0.0';
|
|
5
|
+
app.listen(port, host, () => {
|
|
6
|
+
console.log(`LLM Council running at http://${host}:${port}`);
|
|
7
|
+
});
|
|
8
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;AAE9B,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,SAAS,CAAC;AAC3C,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE;IAC1B,OAAO,CAAC,GAAG,CAAC,iCAAiC,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;AAC/D,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,78 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-llm-council",
|
|
3
|
-
"
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Watch multiple AI models debate side-by-side using GitHub Copilot",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/src/server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"github-llm-council": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"bin",
|
|
13
|
+
"src/public"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsx watch src/server.ts",
|
|
18
|
+
"start": "node dist/src/server.js",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"test:e2e": "playwright test",
|
|
22
|
+
"test:all": "npm run test && npm run test:e2e",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"github",
|
|
27
|
+
"copilot",
|
|
28
|
+
"llm",
|
|
29
|
+
"ai",
|
|
30
|
+
"gpt",
|
|
31
|
+
"claude",
|
|
32
|
+
"gemini",
|
|
33
|
+
"streaming"
|
|
34
|
+
],
|
|
35
|
+
"author": "Varun Ramesh",
|
|
36
|
+
"license": "MIT",
|
|
7
37
|
"repository": {
|
|
8
38
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/varunr89/github-llm-council.git"
|
|
39
|
+
"url": "git+https://github.com/varunr89/github-llm-council.git"
|
|
10
40
|
},
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
},
|
|
14
|
-
"bin": {
|
|
15
|
-
"github-llm-council": "bin/install.js"
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/varunr89/github-llm-council/issues"
|
|
16
43
|
},
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"main": "./out/extension.js",
|
|
21
|
-
"contributes": {
|
|
22
|
-
"commands": [
|
|
23
|
-
{
|
|
24
|
-
"command": "llmCouncil.run",
|
|
25
|
-
"title": "LLM Council"
|
|
26
|
-
}
|
|
27
|
-
],
|
|
28
|
-
"menus": {
|
|
29
|
-
"editor/context": [
|
|
30
|
-
{
|
|
31
|
-
"command": "llmCouncil.run",
|
|
32
|
-
"group": "navigation"
|
|
33
|
-
}
|
|
34
|
-
]
|
|
35
|
-
},
|
|
36
|
-
"configuration": {
|
|
37
|
-
"title": "LLM Council",
|
|
38
|
-
"properties": {
|
|
39
|
-
"llmCouncil.defaultModels": {
|
|
40
|
-
"type": "array",
|
|
41
|
-
"default": ["gpt-5.1", "sonnet-4.5", "gemini-pro-3"],
|
|
42
|
-
"description": "Preferred model ids for council; first available three are used."
|
|
43
|
-
},
|
|
44
|
-
"llmCouncil.historySize": {
|
|
45
|
-
"type": "number",
|
|
46
|
-
"default": 20,
|
|
47
|
-
"description": "Maximum number of run summaries to keep."
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
44
|
+
"homepage": "https://github.com/varunr89/github-llm-council#readme",
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
51
47
|
},
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"test": "npm run compile && npm run test:unit && npm run test:integration",
|
|
57
|
-
"test:unit": "vitest run",
|
|
58
|
-
"test:integration": "vitest run src/test/unit/markdownArtifact.integration.test.ts",
|
|
59
|
-
"lint": "tsc -p ./ --noEmit",
|
|
60
|
-
"package:vsix": "npx vsce package --no-yarn --out dist/llm-council.vsix",
|
|
61
|
-
"prepublishOnly": "npm run compile && npm run package:vsix",
|
|
62
|
-
"postinstall": "node bin/install.js"
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@github/copilot-sdk": "^0.1.13",
|
|
50
|
+
"express": "^5.2.1",
|
|
51
|
+
"open": "^11.0.0"
|
|
63
52
|
},
|
|
64
53
|
"devDependencies": {
|
|
65
|
-
"@
|
|
66
|
-
"@types/
|
|
67
|
-
"@types/
|
|
68
|
-
"@
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"typescript": "^5.0.0",
|
|
75
|
-
"vitest": "^1.6.1",
|
|
76
|
-
"@vscode/vsce": "^2.31.0"
|
|
54
|
+
"@playwright/test": "^1.57.0",
|
|
55
|
+
"@types/express": "^5.0.6",
|
|
56
|
+
"@types/supertest": "^6.0.3",
|
|
57
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
58
|
+
"playwright": "^1.57.0",
|
|
59
|
+
"supertest": "^7.2.2",
|
|
60
|
+
"tsx": "^4.21.0",
|
|
61
|
+
"typescript": "^5.9.3",
|
|
62
|
+
"vitest": "^4.0.17"
|
|
77
63
|
}
|
|
78
64
|
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
const promptInput = document.getElementById('prompt-input');
|
|
2
|
+
const submitBtn = document.getElementById('submit-btn');
|
|
3
|
+
const chips = document.querySelectorAll('.chip');
|
|
4
|
+
const columns = document.querySelectorAll('.council-column');
|
|
5
|
+
const modelSelects = document.querySelectorAll('.model-select');
|
|
6
|
+
|
|
7
|
+
let availableModels = [];
|
|
8
|
+
|
|
9
|
+
// Fetch and populate available models
|
|
10
|
+
async function loadModels() {
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch('/api/models');
|
|
13
|
+
const data = await response.json();
|
|
14
|
+
availableModels = data.models || [];
|
|
15
|
+
|
|
16
|
+
modelSelects.forEach((select, index) => {
|
|
17
|
+
select.textContent = '';
|
|
18
|
+
availableModels.forEach((model, modelIndex) => {
|
|
19
|
+
const option = document.createElement('option');
|
|
20
|
+
option.value = model.id || model;
|
|
21
|
+
option.textContent = model.name || model.id || model;
|
|
22
|
+
// Select different models by default for each column
|
|
23
|
+
if (modelIndex === index % availableModels.length) {
|
|
24
|
+
option.selected = true;
|
|
25
|
+
}
|
|
26
|
+
select.appendChild(option);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
console.log('Loaded models:', availableModels);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Failed to load models:', error);
|
|
33
|
+
modelSelects.forEach(select => {
|
|
34
|
+
select.textContent = '';
|
|
35
|
+
const option = document.createElement('option');
|
|
36
|
+
option.value = '';
|
|
37
|
+
option.textContent = 'Failed to load models';
|
|
38
|
+
select.appendChild(option);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getSelectedModels() {
|
|
44
|
+
return Array.from(modelSelects).map(select => select.value).filter(v => v);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getColumnByIndex(index) {
|
|
48
|
+
return document.querySelector(`[data-index="${index}"]`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resetColumns() {
|
|
52
|
+
columns.forEach(col => {
|
|
53
|
+
col.classList.remove('active', 'done', 'error');
|
|
54
|
+
const responseText = col.querySelector('.response-text');
|
|
55
|
+
responseText.textContent = '';
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function appendCharWithAnimation(column, char) {
|
|
60
|
+
const responseText = column.querySelector('.response-text');
|
|
61
|
+
const span = document.createElement('span');
|
|
62
|
+
span.className = 'char';
|
|
63
|
+
span.textContent = char;
|
|
64
|
+
responseText.appendChild(span);
|
|
65
|
+
|
|
66
|
+
const content = column.querySelector('.column-content');
|
|
67
|
+
content.scrollTop = content.scrollHeight;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function appendTextWithAnimation(column, text) {
|
|
71
|
+
for (const char of text) {
|
|
72
|
+
appendCharWithAnimation(column, char);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function showError(column, message) {
|
|
77
|
+
const responseText = column.querySelector('.response-text');
|
|
78
|
+
responseText.textContent = '';
|
|
79
|
+
const errorSpan = document.createElement('span');
|
|
80
|
+
errorSpan.className = 'error-message';
|
|
81
|
+
errorSpan.textContent = message;
|
|
82
|
+
responseText.appendChild(errorSpan);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function submitPrompt() {
|
|
86
|
+
const prompt = promptInput.value.trim();
|
|
87
|
+
if (!prompt) return;
|
|
88
|
+
|
|
89
|
+
const models = getSelectedModels();
|
|
90
|
+
if (models.length === 0) {
|
|
91
|
+
alert('Please select at least one model');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
resetColumns();
|
|
96
|
+
submitBtn.classList.add('loading');
|
|
97
|
+
submitBtn.disabled = true;
|
|
98
|
+
columns.forEach(col => col.classList.add('active'));
|
|
99
|
+
|
|
100
|
+
// Track completion per column index
|
|
101
|
+
const completedColumns = new Set();
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch('/api/council', {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: { 'Content-Type': 'application/json' },
|
|
107
|
+
body: JSON.stringify({ prompt, models }),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
throw new Error('HTTP ' + response.status);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const reader = response.body.getReader();
|
|
115
|
+
const decoder = new TextDecoder();
|
|
116
|
+
let buffer = '';
|
|
117
|
+
|
|
118
|
+
// Map model ID to column index (models array order matches column order)
|
|
119
|
+
const modelToIndex = {};
|
|
120
|
+
models.forEach((model, idx) => {
|
|
121
|
+
// If same model selected multiple times, we need to track per occurrence
|
|
122
|
+
if (!modelToIndex[model]) {
|
|
123
|
+
modelToIndex[model] = [];
|
|
124
|
+
}
|
|
125
|
+
modelToIndex[model].push(idx);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Track how many "done" events we've seen per model
|
|
129
|
+
const modelDoneCount = {};
|
|
130
|
+
|
|
131
|
+
while (true) {
|
|
132
|
+
const { value, done } = await reader.read();
|
|
133
|
+
if (done) break;
|
|
134
|
+
|
|
135
|
+
buffer += decoder.decode(value, { stream: true });
|
|
136
|
+
const parts = buffer.split('\n\n');
|
|
137
|
+
buffer = parts.pop() || '';
|
|
138
|
+
|
|
139
|
+
for (const part of parts) {
|
|
140
|
+
if (part.startsWith('event: done')) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (part.startsWith('data: ')) {
|
|
145
|
+
try {
|
|
146
|
+
const payload = JSON.parse(part.slice(6));
|
|
147
|
+
|
|
148
|
+
if (payload.error) {
|
|
149
|
+
columns.forEach(col => {
|
|
150
|
+
col.classList.remove('active');
|
|
151
|
+
col.classList.add('error');
|
|
152
|
+
showError(col, 'Error: ' + payload.error);
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const modelId = payload.model;
|
|
158
|
+
const indices = modelToIndex[modelId];
|
|
159
|
+
if (!indices || indices.length === 0) continue;
|
|
160
|
+
|
|
161
|
+
// For models selected multiple times, round-robin the done events
|
|
162
|
+
if (!modelDoneCount[modelId]) modelDoneCount[modelId] = 0;
|
|
163
|
+
|
|
164
|
+
// Get the next column index for this model
|
|
165
|
+
const targetIdx = indices[modelDoneCount[modelId] % indices.length];
|
|
166
|
+
const column = getColumnByIndex(targetIdx);
|
|
167
|
+
if (!column) continue;
|
|
168
|
+
|
|
169
|
+
if (payload.delta) {
|
|
170
|
+
appendTextWithAnimation(column, payload.delta);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (payload.done) {
|
|
174
|
+
column.classList.remove('active');
|
|
175
|
+
column.classList.add('done');
|
|
176
|
+
completedColumns.add(targetIdx);
|
|
177
|
+
modelDoneCount[modelId]++;
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.error('Parse error:', e);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error('Request failed:', error);
|
|
187
|
+
columns.forEach(col => {
|
|
188
|
+
col.classList.remove('active');
|
|
189
|
+
col.classList.add('error');
|
|
190
|
+
showError(col, 'Connection failed');
|
|
191
|
+
});
|
|
192
|
+
} finally {
|
|
193
|
+
submitBtn.classList.remove('loading');
|
|
194
|
+
submitBtn.disabled = false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Event listeners
|
|
199
|
+
submitBtn.addEventListener('click', submitPrompt);
|
|
200
|
+
|
|
201
|
+
promptInput.addEventListener('keydown', function(e) {
|
|
202
|
+
if (e.key === 'Enter') {
|
|
203
|
+
submitPrompt();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
chips.forEach(function(chip) {
|
|
208
|
+
chip.addEventListener('click', function() {
|
|
209
|
+
promptInput.value = chip.dataset.prompt;
|
|
210
|
+
promptInput.classList.add('flash');
|
|
211
|
+
setTimeout(function() {
|
|
212
|
+
promptInput.classList.remove('flash');
|
|
213
|
+
}, 300);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Load models on page load
|
|
218
|
+
loadModels();
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>LLM Council</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
|
10
|
+
<link rel="stylesheet" href="styles.css">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div class="container">
|
|
14
|
+
<header class="header">
|
|
15
|
+
<h1 class="title">
|
|
16
|
+
<span class="title-icon">🔮</span>
|
|
17
|
+
LLM COUNCIL
|
|
18
|
+
</h1>
|
|
19
|
+
<a href="https://github.com" class="github-link" target="_blank">
|
|
20
|
+
<svg height="24" viewBox="0 0 16 16" width="24" fill="currentColor">
|
|
21
|
+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
|
22
|
+
</svg>
|
|
23
|
+
</a>
|
|
24
|
+
</header>
|
|
25
|
+
|
|
26
|
+
<div class="input-section">
|
|
27
|
+
<div class="prompt-chips">
|
|
28
|
+
<button class="chip" data-prompt="Is Rust better than Go?">Is Rust better than Go?</button>
|
|
29
|
+
<button class="chip" data-prompt="Tabs or spaces?">Tabs or spaces?</button>
|
|
30
|
+
<button class="chip" data-prompt="Is AI going to replace developers?">AI replacing devs?</button>
|
|
31
|
+
<button class="chip" data-prompt="Vim or Emacs?">Vim or Emacs?</button>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="input-wrapper">
|
|
34
|
+
<input type="text" id="prompt-input" class="prompt-input" placeholder="Ask a spicy question..." value="Is Rust better than Go?">
|
|
35
|
+
<button id="submit-btn" class="submit-btn">⚡</button>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="council-grid">
|
|
40
|
+
<div class="council-column" data-index="0">
|
|
41
|
+
<div class="column-header">
|
|
42
|
+
<span class="model-indicator"></span>
|
|
43
|
+
<select class="model-select" data-index="0">
|
|
44
|
+
<option value="">Loading models...</option>
|
|
45
|
+
</select>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="column-content">
|
|
48
|
+
<div class="response-text"></div>
|
|
49
|
+
<span class="cursor"></span>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="council-column" data-index="1">
|
|
54
|
+
<div class="column-header">
|
|
55
|
+
<span class="model-indicator"></span>
|
|
56
|
+
<select class="model-select" data-index="1">
|
|
57
|
+
<option value="">Loading models...</option>
|
|
58
|
+
</select>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="column-content">
|
|
61
|
+
<div class="response-text"></div>
|
|
62
|
+
<span class="cursor"></span>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="council-column" data-index="2">
|
|
67
|
+
<div class="column-header">
|
|
68
|
+
<span class="model-indicator"></span>
|
|
69
|
+
<select class="model-select" data-index="2">
|
|
70
|
+
<option value="">Loading models...</option>
|
|
71
|
+
</select>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="column-content">
|
|
74
|
+
<div class="response-text"></div>
|
|
75
|
+
<span class="cursor"></span>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<script src="app.js"></script>
|
|
82
|
+
</body>
|
|
83
|
+
</html>
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg-primary: #0a0a0a;
|
|
3
|
+
--bg-secondary: #111111;
|
|
4
|
+
--bg-tertiary: #1a1a1a;
|
|
5
|
+
--text-primary: #e0e0e0;
|
|
6
|
+
--text-muted: #666666;
|
|
7
|
+
--border-color: #2a2a2a;
|
|
8
|
+
|
|
9
|
+
--gpt-color: #00ffff;
|
|
10
|
+
--claude-color: #ff00ff;
|
|
11
|
+
--gemini-color: #00ff88;
|
|
12
|
+
|
|
13
|
+
--glow-intensity: 0.6;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
* {
|
|
17
|
+
margin: 0;
|
|
18
|
+
padding: 0;
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
body {
|
|
23
|
+
font-family: 'JetBrains Mono', monospace;
|
|
24
|
+
background: var(--bg-primary);
|
|
25
|
+
color: var(--text-primary);
|
|
26
|
+
min-height: 100vh;
|
|
27
|
+
overflow-x: hidden;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.container {
|
|
31
|
+
max-width: 1400px;
|
|
32
|
+
margin: 0 auto;
|
|
33
|
+
padding: 2rem;
|
|
34
|
+
min-height: 100vh;
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Header */
|
|
40
|
+
.header {
|
|
41
|
+
display: flex;
|
|
42
|
+
justify-content: space-between;
|
|
43
|
+
align-items: center;
|
|
44
|
+
margin-bottom: 2rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.title {
|
|
48
|
+
font-size: 1.5rem;
|
|
49
|
+
font-weight: 700;
|
|
50
|
+
letter-spacing: 0.1em;
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
gap: 0.75rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.title-icon {
|
|
57
|
+
font-size: 1.75rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.github-link {
|
|
61
|
+
color: var(--text-muted);
|
|
62
|
+
transition: color 0.2s, filter 0.2s;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.github-link:hover {
|
|
66
|
+
color: var(--text-primary);
|
|
67
|
+
filter: drop-shadow(0 0 8px currentColor);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Input Section */
|
|
71
|
+
.input-section {
|
|
72
|
+
margin-bottom: 2rem;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.prompt-chips {
|
|
76
|
+
display: flex;
|
|
77
|
+
gap: 0.75rem;
|
|
78
|
+
margin-bottom: 1rem;
|
|
79
|
+
flex-wrap: wrap;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.chip {
|
|
83
|
+
background: var(--bg-secondary);
|
|
84
|
+
border: 1px solid var(--border-color);
|
|
85
|
+
color: var(--text-muted);
|
|
86
|
+
padding: 0.5rem 1rem;
|
|
87
|
+
border-radius: 9999px;
|
|
88
|
+
font-family: inherit;
|
|
89
|
+
font-size: 0.8rem;
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
transition: all 0.2s;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.chip:hover {
|
|
95
|
+
border-color: var(--text-muted);
|
|
96
|
+
color: var(--text-primary);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.input-wrapper {
|
|
100
|
+
display: flex;
|
|
101
|
+
gap: 1rem;
|
|
102
|
+
position: relative;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.prompt-input {
|
|
106
|
+
flex: 1;
|
|
107
|
+
background: var(--bg-secondary);
|
|
108
|
+
border: 2px solid var(--border-color);
|
|
109
|
+
color: var(--text-primary);
|
|
110
|
+
padding: 1rem 1.5rem;
|
|
111
|
+
font-family: inherit;
|
|
112
|
+
font-size: 1rem;
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
outline: none;
|
|
115
|
+
transition: all 0.3s;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.prompt-input:focus {
|
|
119
|
+
border-color: var(--gpt-color);
|
|
120
|
+
box-shadow: 0 0 20px rgba(0, 255, 255, 0.2);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.submit-btn {
|
|
124
|
+
background: linear-gradient(135deg, var(--gpt-color), var(--claude-color));
|
|
125
|
+
border: none;
|
|
126
|
+
color: var(--bg-primary);
|
|
127
|
+
padding: 1rem 2rem;
|
|
128
|
+
font-size: 1.5rem;
|
|
129
|
+
border-radius: 8px;
|
|
130
|
+
cursor: pointer;
|
|
131
|
+
transition: all 0.2s;
|
|
132
|
+
font-weight: bold;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.submit-btn:hover {
|
|
136
|
+
transform: scale(1.05);
|
|
137
|
+
box-shadow: 0 0 30px rgba(0, 255, 255, 0.4);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.submit-btn:active {
|
|
141
|
+
transform: scale(0.98);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.submit-btn.loading {
|
|
145
|
+
animation: pulse 1s infinite;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@keyframes pulse {
|
|
149
|
+
0%, 100% { opacity: 1; }
|
|
150
|
+
50% { opacity: 0.6; }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Council Grid */
|
|
154
|
+
.council-grid {
|
|
155
|
+
display: grid;
|
|
156
|
+
grid-template-columns: repeat(3, 1fr);
|
|
157
|
+
gap: 1.5rem;
|
|
158
|
+
flex: 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.council-column {
|
|
162
|
+
background: var(--bg-secondary);
|
|
163
|
+
border: 1px solid var(--border-color);
|
|
164
|
+
border-radius: 12px;
|
|
165
|
+
display: flex;
|
|
166
|
+
flex-direction: column;
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
transition: all 0.3s;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Color by column index */
|
|
172
|
+
.council-column[data-index="0"] {
|
|
173
|
+
--model-color: var(--gpt-color);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.council-column[data-index="1"] {
|
|
177
|
+
--model-color: var(--claude-color);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.council-column[data-index="2"] {
|
|
181
|
+
--model-color: var(--gemini-color);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.council-column.active {
|
|
185
|
+
border-color: var(--model-color);
|
|
186
|
+
box-shadow: 0 0 30px rgba(var(--model-color), 0.2);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.council-column.active .column-header {
|
|
190
|
+
background: linear-gradient(180deg, rgba(255,255,255,0.05) 0%, transparent 100%);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.column-header {
|
|
194
|
+
display: flex;
|
|
195
|
+
align-items: center;
|
|
196
|
+
gap: 0.75rem;
|
|
197
|
+
padding: 1rem 1.25rem;
|
|
198
|
+
border-bottom: 1px solid var(--border-color);
|
|
199
|
+
transition: background 0.3s;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.model-indicator {
|
|
203
|
+
width: 10px;
|
|
204
|
+
height: 10px;
|
|
205
|
+
border-radius: 50%;
|
|
206
|
+
background: var(--model-color);
|
|
207
|
+
opacity: 0.4;
|
|
208
|
+
transition: all 0.3s;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.council-column.active .model-indicator {
|
|
212
|
+
opacity: 1;
|
|
213
|
+
box-shadow: 0 0 10px var(--model-color);
|
|
214
|
+
animation: glow-pulse 2s infinite;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@keyframes glow-pulse {
|
|
218
|
+
0%, 100% { box-shadow: 0 0 10px var(--model-color); }
|
|
219
|
+
50% { box-shadow: 0 0 20px var(--model-color), 0 0 30px var(--model-color); }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.model-name {
|
|
223
|
+
font-size: 0.85rem;
|
|
224
|
+
font-weight: 700;
|
|
225
|
+
letter-spacing: 0.05em;
|
|
226
|
+
color: var(--model-color);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.model-select {
|
|
230
|
+
background: var(--bg-tertiary);
|
|
231
|
+
border: 1px solid var(--border-color);
|
|
232
|
+
color: var(--model-color);
|
|
233
|
+
font-family: inherit;
|
|
234
|
+
font-size: 0.8rem;
|
|
235
|
+
font-weight: 700;
|
|
236
|
+
letter-spacing: 0.02em;
|
|
237
|
+
padding: 0.4rem 0.6rem;
|
|
238
|
+
border-radius: 4px;
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
outline: none;
|
|
241
|
+
transition: all 0.2s;
|
|
242
|
+
min-width: 140px;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.model-select:hover {
|
|
246
|
+
border-color: var(--model-color);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.model-select:focus {
|
|
250
|
+
border-color: var(--model-color);
|
|
251
|
+
box-shadow: 0 0 8px var(--model-color);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.model-select option {
|
|
255
|
+
background: var(--bg-secondary);
|
|
256
|
+
color: var(--text-primary);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.column-content {
|
|
260
|
+
flex: 1;
|
|
261
|
+
padding: 1.25rem;
|
|
262
|
+
overflow-y: auto;
|
|
263
|
+
min-height: 400px;
|
|
264
|
+
position: relative;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.response-text {
|
|
268
|
+
line-height: 1.7;
|
|
269
|
+
font-size: 0.9rem;
|
|
270
|
+
color: var(--text-primary);
|
|
271
|
+
white-space: pre-wrap;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.response-text .char {
|
|
275
|
+
opacity: 0;
|
|
276
|
+
animation: char-appear 0.1s forwards;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@keyframes char-appear {
|
|
280
|
+
from {
|
|
281
|
+
opacity: 0;
|
|
282
|
+
text-shadow: 0 0 10px var(--model-color);
|
|
283
|
+
}
|
|
284
|
+
to {
|
|
285
|
+
opacity: 1;
|
|
286
|
+
text-shadow: none;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.cursor {
|
|
291
|
+
display: none;
|
|
292
|
+
width: 2px;
|
|
293
|
+
height: 1.2em;
|
|
294
|
+
background: var(--model-color);
|
|
295
|
+
animation: blink 0.8s infinite;
|
|
296
|
+
vertical-align: text-bottom;
|
|
297
|
+
margin-left: 2px;
|
|
298
|
+
box-shadow: 0 0 8px var(--model-color);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.council-column.active .cursor {
|
|
302
|
+
display: inline-block;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.council-column.done .cursor {
|
|
306
|
+
display: none;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
@keyframes blink {
|
|
310
|
+
0%, 50% { opacity: 1; }
|
|
311
|
+
51%, 100% { opacity: 0; }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* Flash effect on submit */
|
|
315
|
+
.flash {
|
|
316
|
+
animation: flash-effect 0.3s;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
@keyframes flash-effect {
|
|
320
|
+
0% { filter: brightness(1); }
|
|
321
|
+
50% { filter: brightness(1.5); }
|
|
322
|
+
100% { filter: brightness(1); }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* Error state */
|
|
326
|
+
.council-column.error {
|
|
327
|
+
border-color: #ff4444;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.council-column.error .model-indicator {
|
|
331
|
+
background: #ff4444;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.error-message {
|
|
335
|
+
color: #ff4444;
|
|
336
|
+
font-style: italic;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/* Empty state */
|
|
340
|
+
.column-content .empty-state {
|
|
341
|
+
color: var(--text-muted);
|
|
342
|
+
font-style: italic;
|
|
343
|
+
opacity: 0.5;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* Disable button when loading */
|
|
347
|
+
.submit-btn:disabled {
|
|
348
|
+
opacity: 0.5;
|
|
349
|
+
cursor: not-allowed;
|
|
350
|
+
transform: none !important;
|
|
351
|
+
}
|
package/bin/install.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* eslint-disable no-console */
|
|
3
|
-
const { spawnSync } = require('child_process');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
|
|
7
|
-
const pkgRoot = path.join(__dirname, '..');
|
|
8
|
-
const vsixPath = path.join(pkgRoot, 'dist', 'llm-council.vsix');
|
|
9
|
-
const codeCandidates = [
|
|
10
|
-
process.env.CODE_BIN,
|
|
11
|
-
process.env.VSCODE_BIN,
|
|
12
|
-
'code',
|
|
13
|
-
'code-insiders'
|
|
14
|
-
].filter(Boolean);
|
|
15
|
-
|
|
16
|
-
function runCode(cmd, args) {
|
|
17
|
-
const res = spawnSync(cmd, args, { stdio: 'inherit' });
|
|
18
|
-
return res.status === 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function main() {
|
|
22
|
-
if (!fs.existsSync(vsixPath)) {
|
|
23
|
-
console.log(`[llm-council] VSIX not found at ${vsixPath}, skipping auto-install.`);
|
|
24
|
-
console.log('[llm-council] This is expected in dev; run `npm run package:vsix` before publishing.');
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const args = ['--install-extension', vsixPath, '--force'];
|
|
29
|
-
|
|
30
|
-
for (const candidate of codeCandidates) {
|
|
31
|
-
if (!candidate) continue;
|
|
32
|
-
console.log(`[llm-council] Installing via ${candidate} ${args.join(' ')}`);
|
|
33
|
-
if (runCode(candidate, args)) {
|
|
34
|
-
console.log('[llm-council] Extension installed successfully.');
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
console.error('[llm-council] Failed to install extension. Ensure `code` (VS Code CLI) is on your PATH.');
|
|
40
|
-
console.error('On macOS: run "Shell Command: Install \'code\' command in PATH" from VS Code command palette.');
|
|
41
|
-
console.error('On Windows/Linux: ensure the VS Code bin directory is on PATH.');
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
main();
|
package/dist/llm-council.vsix
DELETED
|
Binary file
|