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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Varun R
3
+ Copyright (c) 2026 Varun Ramesh
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,63 +1,62 @@
1
- # LLM Council (VS Code Extension)
1
+ # LLM Council
2
2
 
3
- Minimal, streaming “council” workflow using the VS Code Language Model API (e.g., Copilot models). Runs three stages, logs streams, and saves a markdown artifact per run.
3
+ Watch multiple AI models debate side-by-side using GitHub Copilot.
4
4
 
5
- Current state
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
- ## Installation
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
- npm install # install deps
18
- npm run package:vsix # builds dist/llm-council.vsix
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
- User install after publish
24
- ```bash
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
- Upgrade
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
- Publish (maintainers)
34
- ```bash
35
- npm install
36
- npm run package:vsix # produce dist/llm-council.vsix
37
- npm pack # produce github-llm-council-0.0.1.tgz
38
- npm publish # prepublishOnly rebuilds/package before publish
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
- ## Usage
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
- Requires: VS Code with LM API-capable extension (e.g., GitHub Copilot) and access to chat models.
31
+ The council can use any model available through GitHub Copilot:
49
32
 
50
- ## Configuration
51
- - `llmCouncil.defaultModels` (array): preferred model ids; first available three are used when no stored selection exists.
52
- - `llmCouncil.historySize` (number): maximum run summaries to retain.
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
- npm run lint
57
- npm run test:unit
58
- npm run test:integration
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
- ## Notes
62
- - Model choices are remembered per workspace between runs.
63
- - Prompts live in `src/prompts.ts`; edit and rebuild/package to distribute changes.
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,2 @@
1
+ export declare function createApp(): Promise<import("express-serve-static-core").Express>;
2
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/app.ts"],"names":[],"mappings":"AAgDA,wBAAsB,SAAS,yDAiK9B"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=server.d.ts.map
@@ -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
- "displayName": "LLM Council",
4
- "description": "VS Code extension for running an LLM council via the LM API",
5
- "version": "0.0.1",
6
- "publisher": "varunr89",
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
- "engines": {
12
- "vscode": "^1.84.0"
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
- "activationEvents": [
18
- "onCommand:llmCouncil.run"
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
- "scripts": {
53
- "vscode:prepublish": "npm run compile",
54
- "compile": "tsc -p ./",
55
- "watch": "tsc -watch -p ./",
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
- "@types/glob": "^7.2.0",
66
- "@types/mocha": "^10.0.0",
67
- "@types/node": "^20.0.0",
68
- "@types/vscode": "^1.84.0",
69
- "@vscode/test-electron": "^2.4.0",
70
- "eslint": "^8.0.0",
71
- "glob": "^7.2.3",
72
- "mocha": "^10.0.0",
73
- "ts-node": "^10.9.2",
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();
Binary file