quorum-ux 0.1.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/LICENSE +21 -0
- package/README.md +196 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +223 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/config/global.d.ts +22 -0
- package/dist/config/global.d.ts.map +1 -0
- package/dist/config/global.js +87 -0
- package/dist/config/global.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +440 -0
- package/dist/index.js.map +1 -0
- package/dist/models/openrouter.d.ts +47 -0
- package/dist/models/openrouter.d.ts.map +1 -0
- package/dist/models/openrouter.js +89 -0
- package/dist/models/openrouter.js.map +1 -0
- package/dist/personas/archetypes.d.ts +9 -0
- package/dist/personas/archetypes.d.ts.map +1 -0
- package/dist/personas/archetypes.js +90 -0
- package/dist/personas/archetypes.js.map +1 -0
- package/dist/personas/index.d.ts +16 -0
- package/dist/personas/index.d.ts.map +1 -0
- package/dist/personas/index.js +38 -0
- package/dist/personas/index.js.map +1 -0
- package/dist/pipeline/analyze-video.d.ts +10 -0
- package/dist/pipeline/analyze-video.d.ts.map +1 -0
- package/dist/pipeline/analyze-video.js +204 -0
- package/dist/pipeline/analyze-video.js.map +1 -0
- package/dist/pipeline/analyze.d.ts +10 -0
- package/dist/pipeline/analyze.d.ts.map +1 -0
- package/dist/pipeline/analyze.js +153 -0
- package/dist/pipeline/analyze.js.map +1 -0
- package/dist/pipeline/extract-frames.d.ts +9 -0
- package/dist/pipeline/extract-frames.d.ts.map +1 -0
- package/dist/pipeline/extract-frames.js +129 -0
- package/dist/pipeline/extract-frames.js.map +1 -0
- package/dist/pipeline/report.d.ts +11 -0
- package/dist/pipeline/report.d.ts.map +1 -0
- package/dist/pipeline/report.js +286 -0
- package/dist/pipeline/report.js.map +1 -0
- package/dist/pipeline/synthesize.d.ts +10 -0
- package/dist/pipeline/synthesize.d.ts.map +1 -0
- package/dist/pipeline/synthesize.js +178 -0
- package/dist/pipeline/synthesize.js.map +1 -0
- package/dist/types.d.ts +225 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/costs.d.ts +52 -0
- package/dist/utils/costs.d.ts.map +1 -0
- package/dist/utils/costs.js +165 -0
- package/dist/utils/costs.js.map +1 -0
- package/dist/utils/files.d.ts +33 -0
- package/dist/utils/files.d.ts.map +1 -0
- package/dist/utils/files.js +88 -0
- package/dist/utils/files.js.map +1 -0
- package/dist/utils/logger.d.ts +15 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +43 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/prompt.d.ts +24 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +95 -0
- package/dist/utils/prompt.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jason Shearer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# QuorumUX
|
|
2
|
+
|
|
3
|
+
**Multi-model consensus UX analysis from E2E test artifacts.**
|
|
4
|
+
|
|
5
|
+
QuorumUX sends your Playwright screenshots and video recordings to multiple AI vision models, then synthesizes their findings into a single prioritized report with consensus-weighted severity. Issues flagged by 2+ models are high confidence. Video analysis catches temporal friction (hesitation, confusion, loading delays) that screenshots miss.
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
<div align="center">
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## What Makes This Different
|
|
16
|
+
|
|
17
|
+
Most visual testing tools compare pixels. QuorumUX asks AI models to think like UX researchers.
|
|
18
|
+
|
|
19
|
+
- **Multi-model consensus** — 3 models analyze independently, a 4th synthesizes. Issues flagged by 2+ models are high confidence. Single-model findings need human review. Disagreements are surfaced explicitly.
|
|
20
|
+
- **Video temporal analysis** — Gemini watches your screen recordings and identifies hesitation (cursor pauses), confusion (backtracking), interaction patterns (rage clicks), and loading delays. This catches friction invisible to static screenshots.
|
|
21
|
+
- **Persona-aware context** — Feed your test runner's persona summaries (pass/fail/friction counts, known issues) into the analysis. Models evaluate against user intent, not just visual state.
|
|
22
|
+
- **Severity synthesis** — Opus weighs evidence from all sources (3 screenshot models + video + test results) and assigns P0/P1/P2 severity with effort estimates.
|
|
23
|
+
- **$3-5 per full run** — 10 personas × 3 models + video + synthesis costs pennies via OpenRouter.
|
|
24
|
+
|
|
25
|
+
## Quickstart
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Install
|
|
29
|
+
npm install quorum-ux
|
|
30
|
+
|
|
31
|
+
# Interactive setup — walks you through API key, personas, and model selection
|
|
32
|
+
npx quorumux init
|
|
33
|
+
|
|
34
|
+
# Preview what the pipeline will do and estimated cost
|
|
35
|
+
npx quorumux --dry-run
|
|
36
|
+
|
|
37
|
+
# Run the full pipeline
|
|
38
|
+
npx quorumux
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or configure manually:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cat > quorumux.config.ts << 'EOF'
|
|
45
|
+
import type { QuorumUXConfig } from 'quorum-ux';
|
|
46
|
+
|
|
47
|
+
const config: QuorumUXConfig = {
|
|
48
|
+
name: 'MyApp',
|
|
49
|
+
description: 'A project management tool for distributed teams',
|
|
50
|
+
domain: 'productivity',
|
|
51
|
+
appUrl: 'https://myapp.com',
|
|
52
|
+
userJourney: 'Signup → Create Project → Invite Team → Assign Tasks → Daily Standup',
|
|
53
|
+
artifactsDir: './test-artifacts',
|
|
54
|
+
models: {
|
|
55
|
+
screenshot: [
|
|
56
|
+
{ id: 'anthropic/claude-sonnet-4.6', name: 'claude' },
|
|
57
|
+
{ id: 'google/gemini-2.0-flash-001', name: 'gemini' },
|
|
58
|
+
{ id: 'openai/gpt-4o-2024-11-20', name: 'gpt4o' },
|
|
59
|
+
],
|
|
60
|
+
video: { id: 'google/gemini-2.0-flash-001', name: 'gemini' },
|
|
61
|
+
synthesis: { id: 'anthropic/claude-opus-4.5', name: 'opus' },
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default config;
|
|
66
|
+
EOF
|
|
67
|
+
|
|
68
|
+
OPENROUTER_API_KEY=sk-or-... npx quorumux
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Prerequisites
|
|
72
|
+
|
|
73
|
+
- **Node.js** >= 18
|
|
74
|
+
- **OpenRouter API key** — [openrouter.ai/keys](https://openrouter.ai/keys)
|
|
75
|
+
- **ffmpeg** — for video frame extraction (`brew install ffmpeg` / `apt install ffmpeg`)
|
|
76
|
+
- **ImageMagick** — for screenshot grids (`brew install imagemagick` / `apt install imagemagick`)
|
|
77
|
+
- **Test artifacts** — screenshots and/or video recordings from your E2E test runner (Playwright recommended)
|
|
78
|
+
|
|
79
|
+
## Artifact Directory Structure
|
|
80
|
+
|
|
81
|
+
QuorumUX expects your test runner to produce artifacts in this structure:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
test-artifacts/
|
|
85
|
+
└── run-2026-02-22T09-00/ # Timestamped run directory
|
|
86
|
+
├── videos/
|
|
87
|
+
│ └── P01-maria/ # One subdir per persona
|
|
88
|
+
│ └── abc123.webm # Playwright video recording
|
|
89
|
+
├── screenshots/
|
|
90
|
+
│ └── P01-maria/
|
|
91
|
+
│ ├── P01-maria-step01-PASS-login.png
|
|
92
|
+
│ ├── P01-maria-step02-PASS-signup.png
|
|
93
|
+
│ └── P01-maria-step03-FRICTION-onboarding.png
|
|
94
|
+
├── summaries/
|
|
95
|
+
│ └── P01-maria-summary.json # Test runner's structured results
|
|
96
|
+
└── executive-summary.md # Optional: test runner's summary
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Naming conventions are flexible — QuorumUX discovers personas from subdirectory names.
|
|
100
|
+
|
|
101
|
+
## CLI Reference
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
npx quorumux [command] [options]
|
|
105
|
+
|
|
106
|
+
Commands:
|
|
107
|
+
init Interactive project setup wizard
|
|
108
|
+
run [options] Run the analysis pipeline (default)
|
|
109
|
+
|
|
110
|
+
Options:
|
|
111
|
+
--config <path> Path to quorumux.config.ts (default: ./quorumux.config.ts)
|
|
112
|
+
--run-dir <path> Specific run directory (auto-detects latest run-*)
|
|
113
|
+
--start-stage <n> Start from stage 1, 2, 3, or 4 (default: 1)
|
|
114
|
+
--skip-video Skip Stage 2b video analysis
|
|
115
|
+
--dry-run Show what would run without making API calls
|
|
116
|
+
--verbose Verbose output
|
|
117
|
+
--help Show help
|
|
118
|
+
|
|
119
|
+
Environment:
|
|
120
|
+
OPENROUTER_API_KEY API key for OpenRouter (preferred).
|
|
121
|
+
Also reads from .env / .env.local or ~/.quorumux/config.json.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Pipeline Stages
|
|
125
|
+
|
|
126
|
+
| Stage | Input | Output | Models Used |
|
|
127
|
+
|-------|-------|--------|-------------|
|
|
128
|
+
| **1: Extract** | videos/, screenshots/ | frames/, grids/ | None (ffmpeg + ImageMagick) |
|
|
129
|
+
| **2: Analyze Screenshots** | grids/, summaries/ | all-analyses-raw.json | All `config.models.screenshot` |
|
|
130
|
+
| **2b: Analyze Video** | videos/, summaries/ | all-video-analyses-raw.json | `config.models.video` |
|
|
131
|
+
| **3: Synthesize** | All Stage 2/2b output + summaries + exec summary | synthesis.json | `config.models.synthesis` |
|
|
132
|
+
| **4: Report** | synthesis.json | ux-analysis-report.md, github-issues.md | None (templating) |
|
|
133
|
+
|
|
134
|
+
Stages 2 and 2b run in parallel. You can start from any stage with `--start-stage`.
|
|
135
|
+
|
|
136
|
+
## Persona Archetypes
|
|
137
|
+
|
|
138
|
+
QuorumUX includes 10 built-in persona archetypes for universal UX testing. Select them during `quorumux init` or reference them in your config:
|
|
139
|
+
|
|
140
|
+
| Archetype | Testing Focus | Device |
|
|
141
|
+
|-----------|--------------|--------|
|
|
142
|
+
| **Happy Path Hero** | Ideal journey, full completion | Desktop |
|
|
143
|
+
| **Speed Runner** | Skip/rush behavior, task efficiency | Desktop |
|
|
144
|
+
| **Cautious Explorer** | Reads everything, hesitates | Desktop |
|
|
145
|
+
| **Mobile-First User** | Touch interactions, small viewport | Mobile |
|
|
146
|
+
| **Accessibility User** | Screen reader, keyboard nav | Desktop |
|
|
147
|
+
| **Distracted Multitasker** | Tab switching, mid-flow pauses | Desktop |
|
|
148
|
+
| **Error-Prone Novice** | Wrong inputs, recovery paths | Desktop |
|
|
149
|
+
| **Power User** | Keyboard shortcuts, advanced features | Desktop |
|
|
150
|
+
| **Skeptical Evaluator** | Edge cases, competitor comparison | Desktop |
|
|
151
|
+
| **International User** | i18n, locale, long text | Desktop |
|
|
152
|
+
|
|
153
|
+
When persona IDs match an archetype, QuorumUX automatically injects behavioral context into the analysis prompts so models know what to look for.
|
|
154
|
+
|
|
155
|
+
## Output: What You Get
|
|
156
|
+
|
|
157
|
+
### ux-analysis-report.md
|
|
158
|
+
|
|
159
|
+
- **Overall assessment**: UX score (1-10), launch readiness, strengths, critical path
|
|
160
|
+
- **Consensus issues**: High-confidence findings from 2+ models, with video insight annotations
|
|
161
|
+
- **Video-only issues**: Temporal friction invisible to screenshots (hesitation, loading, confusion)
|
|
162
|
+
- **Model-unique issues**: Single-model findings that need human review
|
|
163
|
+
- **Disagreements**: Where models actively contradict each other
|
|
164
|
+
|
|
165
|
+
### github-issues.md
|
|
166
|
+
|
|
167
|
+
Ready-to-paste `gh issue create` commands for every finding, with severity labels and structured descriptions.
|
|
168
|
+
|
|
169
|
+
### synthesis.json
|
|
170
|
+
|
|
171
|
+
Raw structured data for programmatic consumption or custom reporting.
|
|
172
|
+
|
|
173
|
+
## Integrating with Your Test Runner
|
|
174
|
+
|
|
175
|
+
QuorumUX is test-runner agnostic. It consumes artifacts, not test code. Any runner that produces screenshots and/or video works:
|
|
176
|
+
|
|
177
|
+
- **Playwright** (recommended): Use `recordVideo` on browser context + `page.screenshot()` at checkpoints
|
|
178
|
+
- **Cypress**: Use `cy.screenshot()` + video recording config
|
|
179
|
+
- **Puppeteer**: Use `page.screenshot()` + screen recording via Chrome DevTools Protocol
|
|
180
|
+
|
|
181
|
+
The optional `summaries/*.json` files give QuorumUX additional context (pass/fail counts, known issues) but aren't required. Without them, analysis is purely visual.
|
|
182
|
+
|
|
183
|
+
## Cost
|
|
184
|
+
|
|
185
|
+
Typical cost for a 10-persona run via OpenRouter:
|
|
186
|
+
|
|
187
|
+
| Stage | Models | ~Cost |
|
|
188
|
+
|-------|--------|-------|
|
|
189
|
+
| Screenshots | 3 models × 10 personas | ~$1.50 |
|
|
190
|
+
| Video | Gemini × 10-12 videos | ~$0.50 |
|
|
191
|
+
| Synthesis | Opus × 1 call (~60K tokens) | ~$1.50 |
|
|
192
|
+
| **Total** | | **~$3.50** |
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAkEH,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAgE7C"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `quorumux init` — Interactive Project Setup Wizard
|
|
3
|
+
*
|
|
4
|
+
* Walks the user through configuring a new project and generates quorumux.config.ts.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { ask, select, confirm, closePrompt } from '../utils/prompt';
|
|
9
|
+
import { resolveApiKey, loadGlobalConfig, saveGlobalConfig } from '../config/global';
|
|
10
|
+
import { PERSONA_BUNDLES, getArchetypeSubset } from '../personas';
|
|
11
|
+
const DOMAINS = [
|
|
12
|
+
{ label: 'Career Tech', value: 'career-tech' },
|
|
13
|
+
{ label: 'Fintech', value: 'fintech' },
|
|
14
|
+
{ label: 'Healthcare', value: 'healthcare' },
|
|
15
|
+
{ label: 'E-commerce', value: 'e-commerce' },
|
|
16
|
+
{ label: 'SaaS', value: 'saas' },
|
|
17
|
+
{ label: 'Social', value: 'social' },
|
|
18
|
+
{ label: 'Education', value: 'education' },
|
|
19
|
+
{ label: 'Other', value: 'other' },
|
|
20
|
+
];
|
|
21
|
+
const TESTING_DEPTHS = [
|
|
22
|
+
{
|
|
23
|
+
label: 'Fast (~$1)',
|
|
24
|
+
value: 'fast',
|
|
25
|
+
description: '1 screenshot model, no video, Gemini synthesis',
|
|
26
|
+
models: {
|
|
27
|
+
screenshot: [{ id: 'google/gemini-2.0-flash-001', name: 'gemini' }],
|
|
28
|
+
video: { id: 'google/gemini-2.0-flash-001', name: 'gemini' },
|
|
29
|
+
synthesis: { id: 'google/gemini-2.0-flash-001', name: 'gemini' },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: 'Balanced (~$4)',
|
|
34
|
+
value: 'balanced',
|
|
35
|
+
description: '3 screenshot models, video, Opus synthesis (recommended)',
|
|
36
|
+
models: {
|
|
37
|
+
screenshot: [
|
|
38
|
+
{ id: 'anthropic/claude-sonnet-4.6', name: 'claude' },
|
|
39
|
+
{ id: 'google/gemini-2.0-flash-001', name: 'gemini' },
|
|
40
|
+
{ id: 'openai/gpt-4o-2024-11-20', name: 'gpt4o' },
|
|
41
|
+
],
|
|
42
|
+
video: { id: 'google/gemini-2.0-flash-001', name: 'gemini' },
|
|
43
|
+
synthesis: { id: 'anthropic/claude-opus-4.5', name: 'opus' },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
label: 'Thorough (~$8)',
|
|
48
|
+
value: 'thorough',
|
|
49
|
+
description: '3 screenshot models (high token limits), video, Opus synthesis with extended analysis',
|
|
50
|
+
models: {
|
|
51
|
+
screenshot: [
|
|
52
|
+
{ id: 'anthropic/claude-sonnet-4.6', name: 'claude', maxTokens: 5000 },
|
|
53
|
+
{ id: 'google/gemini-2.0-flash-001', name: 'gemini', maxTokens: 5000 },
|
|
54
|
+
{ id: 'openai/gpt-4o-2024-11-20', name: 'gpt4o', maxTokens: 5000 },
|
|
55
|
+
],
|
|
56
|
+
video: { id: 'google/gemini-2.0-flash-001', name: 'gemini', maxTokens: 6000 },
|
|
57
|
+
synthesis: { id: 'anthropic/claude-opus-4.5', name: 'opus', maxTokens: 12000 },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
export async function runInit() {
|
|
62
|
+
console.log('\n QuorumUX — Project Setup\n');
|
|
63
|
+
try {
|
|
64
|
+
// Step 1: API Key
|
|
65
|
+
const apiKey = await resolveApiKeyStep();
|
|
66
|
+
// Step 2: Project Basics
|
|
67
|
+
const name = await ask('Project name');
|
|
68
|
+
const description = await ask('One-line description');
|
|
69
|
+
const domain = await select('What domain is your app in?', [...DOMAINS]);
|
|
70
|
+
const appUrl = await ask('App URL (e.g. https://myapp.com)');
|
|
71
|
+
// Step 3: User Journey
|
|
72
|
+
const userJourney = await ask('Describe the primary user flow to test');
|
|
73
|
+
// Step 4: Personas
|
|
74
|
+
const personaIds = await selectPersonas();
|
|
75
|
+
// Step 5: Testing Depth
|
|
76
|
+
const depth = await select('Testing depth?', TESTING_DEPTHS);
|
|
77
|
+
const models = TESTING_DEPTHS.find((d) => d.value === depth).models;
|
|
78
|
+
// Step 6: Artifacts Directory
|
|
79
|
+
const artifactsDir = await ask('Artifacts directory', './test-artifacts');
|
|
80
|
+
// Step 7: Skip video?
|
|
81
|
+
const skipVideo = depth === 'fast';
|
|
82
|
+
// Generate config file
|
|
83
|
+
const configContent = generateConfig({
|
|
84
|
+
name,
|
|
85
|
+
description,
|
|
86
|
+
domain,
|
|
87
|
+
appUrl,
|
|
88
|
+
userJourney,
|
|
89
|
+
artifactsDir,
|
|
90
|
+
models,
|
|
91
|
+
personaIds,
|
|
92
|
+
skipVideo,
|
|
93
|
+
});
|
|
94
|
+
const configPath = path.join(process.cwd(), 'quorumux.config.ts');
|
|
95
|
+
if (fs.existsSync(configPath)) {
|
|
96
|
+
const overwrite = await confirm('quorumux.config.ts already exists. Overwrite?', false);
|
|
97
|
+
if (!overwrite) {
|
|
98
|
+
console.log('\n Aborted. Existing config preserved.\n');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
fs.writeFileSync(configPath, configContent);
|
|
103
|
+
console.log('\n Config written to quorumux.config.ts');
|
|
104
|
+
console.log(` Selected ${personaIds.length} persona archetype(s): ${personaIds.join(', ')}`);
|
|
105
|
+
console.log(` Testing depth: ${depth}`);
|
|
106
|
+
console.log(`\n Next steps:`);
|
|
107
|
+
console.log(` 1. Place test artifacts in ${artifactsDir}/`);
|
|
108
|
+
console.log(` 2. Run: npx quorumux --dry-run`);
|
|
109
|
+
console.log(` 3. Run: npx quorumux\n`);
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
closePrompt();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function resolveApiKeyStep() {
|
|
116
|
+
const existing = resolveApiKey();
|
|
117
|
+
if (existing) {
|
|
118
|
+
console.log(' API key found.\n');
|
|
119
|
+
return existing;
|
|
120
|
+
}
|
|
121
|
+
console.log(' No OpenRouter API key found.\n');
|
|
122
|
+
console.log(' Get one at: https://openrouter.ai/keys\n');
|
|
123
|
+
const key = await ask('Enter your OpenRouter API key');
|
|
124
|
+
if (!key) {
|
|
125
|
+
console.log('\n No key provided. You can set OPENROUTER_API_KEY later.\n');
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
const saveChoice = await select('Where should the key be stored?', [
|
|
129
|
+
{
|
|
130
|
+
label: 'Global config (~/.quorumux/config.json)',
|
|
131
|
+
value: 'global',
|
|
132
|
+
description: 'Convenient but stored in plaintext',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
label: 'Show env var command (recommended)',
|
|
136
|
+
value: 'env',
|
|
137
|
+
description: 'More secure, add to your shell profile',
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
if (saveChoice === 'global') {
|
|
141
|
+
const config = loadGlobalConfig();
|
|
142
|
+
config.apiKey = key;
|
|
143
|
+
saveGlobalConfig(config);
|
|
144
|
+
process.env.OPENROUTER_API_KEY = key;
|
|
145
|
+
console.log('\n Saved to ~/.quorumux/config.json\n');
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.log(`\n Add this to your shell profile (.zshrc, .bashrc, etc.):`);
|
|
149
|
+
console.log(` export OPENROUTER_API_KEY="${key}"\n`);
|
|
150
|
+
process.env.OPENROUTER_API_KEY = key;
|
|
151
|
+
}
|
|
152
|
+
return key;
|
|
153
|
+
}
|
|
154
|
+
async function selectPersonas() {
|
|
155
|
+
const bundle = await select('Which persona set?', [
|
|
156
|
+
{ label: 'Quick (3)', value: 'quick', description: 'happy-path, mobile-first, accessibility' },
|
|
157
|
+
{ label: 'Standard (5)', value: 'standard', description: '+ speed-runner, novice' },
|
|
158
|
+
{
|
|
159
|
+
label: 'Comprehensive (8)',
|
|
160
|
+
value: 'comprehensive',
|
|
161
|
+
description: '+ cautious-explorer, power-user, skeptic',
|
|
162
|
+
},
|
|
163
|
+
{ label: 'Full (10)', value: 'full', description: 'All archetypes' },
|
|
164
|
+
]);
|
|
165
|
+
// "Custom" would be handled via the "Other" auto-option in a real interactive UI,
|
|
166
|
+
// but for now bundles cover the main use cases
|
|
167
|
+
const ids = PERSONA_BUNDLES[bundle];
|
|
168
|
+
// Validate they all exist
|
|
169
|
+
getArchetypeSubset([...ids]);
|
|
170
|
+
return [...ids];
|
|
171
|
+
}
|
|
172
|
+
function generateConfig(input) {
|
|
173
|
+
const modelSpecToString = (spec, indent) => {
|
|
174
|
+
const parts = [`id: '${spec.id}'`, `name: '${spec.name}'`];
|
|
175
|
+
if (spec.maxTokens)
|
|
176
|
+
parts.push(`maxTokens: ${spec.maxTokens}`);
|
|
177
|
+
return `${indent}{ ${parts.join(', ')} }`;
|
|
178
|
+
};
|
|
179
|
+
const screenshotModels = input.models.screenshot
|
|
180
|
+
.map((m) => modelSpecToString(m, ' '))
|
|
181
|
+
.join(',\n');
|
|
182
|
+
const videoModel = modelSpecToString(input.models.video, ' ');
|
|
183
|
+
const synthesisModel = modelSpecToString(input.models.synthesis, ' ');
|
|
184
|
+
const personaArray = input.personaIds.map((id) => `'${id}'`).join(', ');
|
|
185
|
+
return `import type { QuorumUXConfig } from './src/types';
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* ${input.name} — QuorumUX Configuration
|
|
189
|
+
*
|
|
190
|
+
* Generated by \`quorumux init\`
|
|
191
|
+
*/
|
|
192
|
+
const config: QuorumUXConfig = {
|
|
193
|
+
name: '${escapeStr(input.name)}',
|
|
194
|
+
description: '${escapeStr(input.description)}',
|
|
195
|
+
domain: '${escapeStr(input.domain)}',
|
|
196
|
+
appUrl: '${escapeStr(input.appUrl)}',
|
|
197
|
+
userJourney: '${escapeStr(input.userJourney)}',
|
|
198
|
+
artifactsDir: '${escapeStr(input.artifactsDir)}',
|
|
199
|
+
|
|
200
|
+
/** Persona archetypes selected during init */
|
|
201
|
+
personas: [${personaArray}],
|
|
202
|
+
|
|
203
|
+
models: {
|
|
204
|
+
screenshot: [
|
|
205
|
+
${screenshotModels},
|
|
206
|
+
],
|
|
207
|
+
video: ${videoModel},
|
|
208
|
+
synthesis: ${synthesisModel},
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
video: {
|
|
212
|
+
maxSizeMB: 20,
|
|
213
|
+
frameRate: 1,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
export default config;
|
|
218
|
+
`;
|
|
219
|
+
}
|
|
220
|
+
function escapeStr(s) {
|
|
221
|
+
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Config Management (~/.quorumux/config.json)
|
|
3
|
+
*
|
|
4
|
+
* Handles global settings that persist across projects:
|
|
5
|
+
* - API key storage (env var preferred, global config as fallback)
|
|
6
|
+
* - Default model configuration
|
|
7
|
+
*/
|
|
8
|
+
import { GlobalConfig } from '../types';
|
|
9
|
+
/** Load global config from ~/.quorumux/config.json. Returns empty config if not found. */
|
|
10
|
+
export declare function loadGlobalConfig(): GlobalConfig;
|
|
11
|
+
/** Save global config to ~/.quorumux/config.json. Creates ~/.quorumux/ if needed. */
|
|
12
|
+
export declare function saveGlobalConfig(config: GlobalConfig): void;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve API key using the priority chain:
|
|
15
|
+
* 1. OPENROUTER_API_KEY env var
|
|
16
|
+
* 2. .env / .env.local in project dir
|
|
17
|
+
* 3. ~/.quorumux/config.json → apiKey
|
|
18
|
+
*
|
|
19
|
+
* Returns the key or undefined if not found anywhere.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveApiKey(): string | undefined;
|
|
22
|
+
//# sourceMappingURL=global.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../../src/config/global.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAKxC,0FAA0F;AAC1F,wBAAgB,gBAAgB,IAAI,YAAY,CAW/C;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAK3D;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAqBlD"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global Config Management (~/.quorumux/config.json)
|
|
3
|
+
*
|
|
4
|
+
* Handles global settings that persist across projects:
|
|
5
|
+
* - API key storage (env var preferred, global config as fallback)
|
|
6
|
+
* - Default model configuration
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
const QUORUMUX_DIR = path.join(process.env.HOME || '~', '.quorumux');
|
|
11
|
+
const CONFIG_PATH = path.join(QUORUMUX_DIR, 'config.json');
|
|
12
|
+
/** Load global config from ~/.quorumux/config.json. Returns empty config if not found. */
|
|
13
|
+
export function loadGlobalConfig() {
|
|
14
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
|
|
19
|
+
return JSON.parse(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Save global config to ~/.quorumux/config.json. Creates ~/.quorumux/ if needed. */
|
|
26
|
+
export function saveGlobalConfig(config) {
|
|
27
|
+
if (!fs.existsSync(QUORUMUX_DIR)) {
|
|
28
|
+
fs.mkdirSync(QUORUMUX_DIR, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve API key using the priority chain:
|
|
34
|
+
* 1. OPENROUTER_API_KEY env var
|
|
35
|
+
* 2. .env / .env.local in project dir
|
|
36
|
+
* 3. ~/.quorumux/config.json → apiKey
|
|
37
|
+
*
|
|
38
|
+
* Returns the key or undefined if not found anywhere.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveApiKey() {
|
|
41
|
+
// 1. Env var (highest priority)
|
|
42
|
+
if (process.env.OPENROUTER_API_KEY) {
|
|
43
|
+
return process.env.OPENROUTER_API_KEY;
|
|
44
|
+
}
|
|
45
|
+
// 2. .env / .env.local in cwd
|
|
46
|
+
const envKey = readEnvFile('.env.local') || readEnvFile('.env');
|
|
47
|
+
if (envKey) {
|
|
48
|
+
process.env.OPENROUTER_API_KEY = envKey;
|
|
49
|
+
return envKey;
|
|
50
|
+
}
|
|
51
|
+
// 3. Global config
|
|
52
|
+
const globalConfig = loadGlobalConfig();
|
|
53
|
+
if (globalConfig.apiKey) {
|
|
54
|
+
process.env.OPENROUTER_API_KEY = globalConfig.apiKey;
|
|
55
|
+
return globalConfig.apiKey;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
/** Read OPENROUTER_API_KEY from a dotenv file (simple parser, no deps) */
|
|
60
|
+
function readEnvFile(filename) {
|
|
61
|
+
const filePath = path.join(process.cwd(), filename);
|
|
62
|
+
if (!fs.existsSync(filePath))
|
|
63
|
+
return undefined;
|
|
64
|
+
try {
|
|
65
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
66
|
+
for (const line of content.split('\n')) {
|
|
67
|
+
const trimmed = line.trim();
|
|
68
|
+
if (trimmed.startsWith('#') || !trimmed.includes('='))
|
|
69
|
+
continue;
|
|
70
|
+
const eqIndex = trimmed.indexOf('=');
|
|
71
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
72
|
+
if (key === 'OPENROUTER_API_KEY') {
|
|
73
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
74
|
+
// Strip surrounding quotes
|
|
75
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
76
|
+
value = value.slice(1, -1);
|
|
77
|
+
}
|
|
78
|
+
return value || undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Ignore read errors
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=global.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global.js","sourceRoot":"","sources":["../../src/config/global.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* QuorumUX CLI — Entry Point
|
|
4
|
+
*
|
|
5
|
+
* Subcommands:
|
|
6
|
+
* init Interactive project setup wizard
|
|
7
|
+
* run [opts] Run the analysis pipeline (default)
|
|
8
|
+
* --help Show help
|
|
9
|
+
*
|
|
10
|
+
* Orchestrates the 4-stage UX analysis pipeline:
|
|
11
|
+
* Stage 1: Extract frames and generate grids from videos
|
|
12
|
+
* Stage 2: Analyze screenshots with multiple AI models
|
|
13
|
+
* Stage 2b: Analyze videos for temporal insights (parallel with Stage 2)
|
|
14
|
+
* Stage 3: Synthesize analyses across models and test data
|
|
15
|
+
* Stage 4: Generate reports and GitHub issues
|
|
16
|
+
*/
|
|
17
|
+
export type { QuorumUXConfig, ModelConfig, ModelSpec, VideoConfig, PersonaArchetype } from './types';
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG;AAQH,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC"}
|