analyzthis_design 2.2.0 → 2.3.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.
@@ -0,0 +1,745 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * MCP (Model Context Protocol) server for Analyzthis Design.
6
+ *
7
+ * Exposes every persona, combination pass, and utility as an MCP tool
8
+ * that any MCP-compatible client can discover and call — Cursor, Claude
9
+ * Desktop, Lovable, v0, Bolt, Replit, ChatGPT, Windsurf, etc.
10
+ *
11
+ * Runs locally via stdio. No hosting, no API keys, no network calls.
12
+ *
13
+ * Protocol: JSON-RPC 2.0 over stdio (MCP spec)
14
+ * Zero external dependencies.
15
+ *
16
+ * Usage:
17
+ * npx analyzthis_design mcp # start stdio server
18
+ * npx analyzthis_design mcp --configure cursor # write MCP config
19
+ * npx analyzthis_design mcp --configure claude # write Claude Desktop config
20
+ */
21
+
22
+ var fs = require('fs');
23
+ var path = require('path');
24
+ var os = require('os');
25
+ var readline = require('readline');
26
+
27
+ var { resolvePackageRoot } = require('./platforms');
28
+ var PACKAGE_ROOT = resolvePackageRoot(__dirname);
29
+ var VERSION = '2.2.1';
30
+ try { VERSION = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8')).version; } catch (e) {}
31
+
32
+ // ── Tool definitions ─────────────────────────────────────────────────────
33
+
34
+ var TOOLS = [
35
+ // ── Individual personas ──
36
+ {
37
+ name: 'analyzthis_arjun',
38
+ description: 'Arjun — UX + Visual Design audit. Runs the UX Honeycomb (Useful, Usable, Findable, Credible, Accessible, Desirable, Valuable) AND a full Visual Design Audit (hierarchy, color, typography, spacing, components, style fit, micro-interactions). Use for any visual or UX critique.',
39
+ inputSchema: {
40
+ type: 'object',
41
+ properties: {
42
+ task: { type: 'string', description: 'What to critique (e.g., "Review the login page contrast and hierarchy")' },
43
+ context: { type: 'string', description: 'Additional context: design system tokens, product type, user role, screenshot description' },
44
+ },
45
+ required: ['task'],
46
+ },
47
+ },
48
+ {
49
+ name: 'analyzthis_meera',
50
+ description: 'Meera — Business / Retention / GTM lens. Evaluates whether a design moves the north-star metric, assesses adoption risk, and checks competitive parity vs differentiation. Use for business-impact critiques.',
51
+ inputSchema: {
52
+ type: 'object',
53
+ properties: {
54
+ task: { type: 'string', description: 'What to evaluate (e.g., "Does this onboarding flow improve activation rate?")' },
55
+ context: { type: 'string', description: 'Business context: north-star metric, target segment, competitive landscape' },
56
+ },
57
+ required: ['task'],
58
+ },
59
+ },
60
+ {
61
+ name: 'analyzthis_priya',
62
+ description: 'Priya — Feasibility / Engineering effort. Sizes technical complexity (S/M/L/XL), identifies hard blockers, flags implementation risks, and suggests simpler 80% alternatives. Stack-specific guidance for 16 frameworks. Use for effort estimation.',
63
+ inputSchema: {
64
+ type: 'object',
65
+ properties: {
66
+ task: { type: 'string', description: 'What to assess (e.g., "How much effort to build this settings page in Next.js?")' },
67
+ stack: { type: 'string', description: 'Tech stack (e.g., "nextjs", "react", "vue", "flutter", "swiftui")' },
68
+ context: { type: 'string', description: 'Technical context: existing components, API constraints, team size' },
69
+ },
70
+ required: ['task'],
71
+ },
72
+ },
73
+ {
74
+ name: 'analyzthis_zara',
75
+ description: 'Zara — Delight moments. Identifies exactly ONE structural or surface delight moment in a design. Covers first impressions, onboarding flows, empty states, peak moments, and once-ever experiences. Use after the core critique is done.',
76
+ inputSchema: {
77
+ type: 'object',
78
+ properties: {
79
+ task: { type: 'string', description: 'What to find delight in (e.g., "Find a delight moment in the empty state")' },
80
+ surface_type: { type: 'string', description: 'Surface type: onboarding, empty_state, error_state, success_state, peak_moment, first_impression' },
81
+ high_frequency: { type: 'boolean', description: 'If true, Zara may say "speed is the craft" instead of adding delight' },
82
+ },
83
+ required: ['task'],
84
+ },
85
+ },
86
+ {
87
+ name: 'analyzthis_noor',
88
+ description: 'Noor — Minimalist IA. Designs task-first information architecture with progressive disclosure, single primary actions, and ≤3 navigation levels. Produces Concept A (minimalist wireframe). Use for IA design and wireframe generation.',
89
+ inputSchema: {
90
+ type: 'object',
91
+ properties: {
92
+ task: { type: 'string', description: 'What to design (e.g., "Design a settings page for B2B SaaS")' },
93
+ context: { type: 'string', description: 'Product context: user role, frequency, entity volume, existing nav structure' },
94
+ },
95
+ required: ['task'],
96
+ },
97
+ },
98
+ {
99
+ name: 'analyzthis_anuj',
100
+ description: 'Anuj — Power-user IA. Designs dense, expert-optimized information architecture with bulk operations, keyboard shortcuts, and full data density. Produces Concept B (power-user wireframe). Use for expert-user interface design.',
101
+ inputSchema: {
102
+ type: 'object',
103
+ properties: {
104
+ task: { type: 'string', description: 'What to design (e.g., "Design a power-user settings page with bulk actions")' },
105
+ context: { type: 'string', description: 'Power-user context: daily frequency, entity volume, keyboard-first, bulk operations needed' },
106
+ },
107
+ required: ['task'],
108
+ },
109
+ },
110
+ {
111
+ name: 'analyzthis_raj',
112
+ description: 'Raj — Product strategy and stalemate arbitrator. Resolves disagreements between personas using 5 ranked product principles. Also challenges the task premise — "are we solving the right problem?" Use when personas disagree or to validate the framing.',
113
+ inputSchema: {
114
+ type: 'object',
115
+ properties: {
116
+ task: { type: 'string', description: 'What to arbitrate or validate (e.g., "Noor and Anuj disagree on navigation depth")' },
117
+ contested_dimensions: { type: 'array', items: { type: 'string' }, description: 'Dimensions being contested' },
118
+ context: { type: 'string', description: 'PRD evidence, user data, or prior persona outputs' },
119
+ },
120
+ required: ['task'],
121
+ },
122
+ },
123
+ {
124
+ name: 'analyzthis_kavi',
125
+ description: 'Kavi — Knowledge archivist. Scans the codebase, discovers Obsidian vaults, fetches web references, builds a vault, enriches notes, and syncs the knowledge bank. Use to index a project for personas.',
126
+ inputSchema: {
127
+ type: 'object',
128
+ properties: {
129
+ action: { type: 'string', enum: ['collect', 'sync', 'status', 'connect', 'disconnect'], description: 'Action to perform' },
130
+ vault_path: { type: 'string', description: 'Path to Obsidian vault (for connect/disconnect)' },
131
+ tags: { type: 'string', description: 'Comma-separated tags to filter by (for connect)' },
132
+ include: { type: 'string', description: 'Comma-separated sub-folders to include (for connect)' },
133
+ },
134
+ required: ['action'],
135
+ },
136
+ },
137
+
138
+ // ── Combination passes ──
139
+ {
140
+ name: 'analyzthis_ux_ideator',
141
+ description: 'UX Ideator — 6-phase ideation workflow. Meera (business reframe) → Noor + Anuj (IA audit + two competing wireframes) → Deliberation (Noor vs Anuj, Raj on standby) → Arjun (UX validation) → Zara (delight) → Priya (feasibility). Output: one deliberated, implementation-ready concept. Use for "design a new screen" or "wireframe a feature".',
142
+ inputSchema: {
143
+ type: 'object',
144
+ properties: {
145
+ task: { type: 'string', description: 'What to design (e.g., "Design a notifications settings page for our B2B SaaS")' },
146
+ product_name: { type: 'string', description: 'Product name' },
147
+ primary_user: { type: 'string', description: 'Primary user role and session frequency' },
148
+ north_star: { type: 'string', description: 'North-star metric' },
149
+ tech_stack: { type: 'string', description: 'Tech stack + design system' },
150
+ high_frequency: { type: 'boolean', description: 'Is this a high-frequency working surface?' },
151
+ out_of_scope: { type: 'string', description: 'What is out of scope' },
152
+ },
153
+ required: ['task'],
154
+ },
155
+ },
156
+ {
157
+ name: 'analyzthis_persona_orchestrator',
158
+ description: 'Persona Orchestrator — Full MoE critique. Routes to the right personas based on the task, runs adversarial deliberation, and produces a composite score + SHIP/REVISE/BLOCK verdict + top 3 fixes. Use for "review this screen" or "critique this design".',
159
+ inputSchema: {
160
+ type: 'object',
161
+ properties: {
162
+ task: { type: 'string', description: 'What to critique (e.g., "Review this onboarding flow for UX friction and hierarchy")' },
163
+ figma_url: { type: 'string', description: 'Figma URL (optional)' },
164
+ full: { type: 'boolean', description: 'Run full 4-persona chain (default: lite MoE subset)' },
165
+ context: { type: 'string', description: 'Additional context: design system, product type, known issues' },
166
+ },
167
+ required: ['task'],
168
+ },
169
+ },
170
+ {
171
+ name: 'analyzthis_design_critic',
172
+ description: 'Design Critic — 4-persona scored review. Arjun (UX + Visual) + Meera (Business) + Priya (Feasibility) + Zara (Delight) each score the design, deliberate, and produce a composite score with hierarchy gate. Use for structured, scored critiques.',
173
+ inputSchema: {
174
+ type: 'object',
175
+ properties: {
176
+ task: { type: 'string', description: 'What to critique' },
177
+ full: { type: 'boolean', description: 'Run all 4 personas in full mode (default: lite)' },
178
+ context: { type: 'string', description: 'Design context: tokens, components, product type' },
179
+ },
180
+ required: ['task'],
181
+ },
182
+ },
183
+ {
184
+ name: 'analyzthis_ux_story_gate',
185
+ description: 'UX Story Gate — Task-first screen review. Discovers PRDs and user stories from the knowledge bank, runs a design-system/Figma discovery pass, routes to the right personas, and ends with a browser verify gate. Use when you want a task-grounded review tied to user stories.',
186
+ inputSchema: {
187
+ type: 'object',
188
+ properties: {
189
+ task: { type: 'string', description: 'What to review' },
190
+ figma_url: { type: 'string', description: 'Figma URL (optional)' },
191
+ context: { type: 'string', description: 'PRDs, user stories, design system info' },
192
+ },
193
+ required: ['task'],
194
+ },
195
+ },
196
+ {
197
+ name: 'analyzthis_design_director',
198
+ description: 'Design Director — Full producer path. Ideation → DesignSpec (tokens + components + states) → spec gates (DS + hierarchy + visual) → implement when approved. Use for end-to-end design + spec + build.',
199
+ inputSchema: {
200
+ type: 'object',
201
+ properties: {
202
+ task: { type: 'string', description: 'What to design (e.g., "Design a notifications settings page with our shadcn components")' },
203
+ build_approved: { type: 'boolean', description: 'If true, implementation begins after spec gates pass' },
204
+ context: { type: 'string', description: 'Design system tokens, component library, tech stack' },
205
+ },
206
+ required: ['task'],
207
+ },
208
+ },
209
+ {
210
+ name: 'analyzthis_mood_board',
211
+ description: 'Mood Board — Visual direction. Collects web references + design-system patterns, tags them by style/mood/surface, and runs team deliberation (Arjun, Meera, Priya, Zara, Noor) using the UX Honeycomb matrix. Use for setting visual direction before designing.',
212
+ inputSchema: {
213
+ type: 'object',
214
+ properties: {
215
+ task: { type: 'string', description: 'Visual direction brief (e.g., "B2B fintech dashboard, trustworthy, high-contrast, data-dense")' },
216
+ urls: { type: 'array', items: { type: 'string' }, description: 'Web reference URLs to include' },
217
+ auto: { type: 'boolean', description: 'Auto-collect references' },
218
+ },
219
+ required: ['task'],
220
+ },
221
+ },
222
+ {
223
+ name: 'analyzthis_evolve_check',
224
+ description: 'Evolve Check — Team evolution dashboard. Shows per-persona evolution scores (0-100, Novice → Expert), checks if enough data exists to propose patches, and summarizes pending patches. Use after critique runs to track team growth.',
225
+ inputSchema: {
226
+ type: 'object',
227
+ properties: {
228
+ action: { type: 'string', enum: ['metrics', 'ready', 'extract', 'apply'], description: 'metrics: show dashboard; ready: check if evolution is ready; extract: propose patches; apply: apply a patch' },
229
+ patch_id: { type: 'string', description: 'Patch ID (for apply action)' },
230
+ },
231
+ required: ['action'],
232
+ },
233
+ },
234
+
235
+ // ── Utility tools ──
236
+ {
237
+ name: 'analyzthis_collect',
238
+ description: 'Kavi collect — Scan the codebase, discover knowledge sources (Obsidian vaults, wikis), fetch web URLs, enrich notes with LLM, and sync everything into the knowledge bank. Run once per project before using personas.',
239
+ inputSchema: {
240
+ type: 'object',
241
+ properties: {
242
+ vault_path: { type: 'string', description: 'Path to an existing Obsidian vault to connect' },
243
+ no_web: { type: 'boolean', description: 'Skip web URL fetching' },
244
+ no_enrich: { type: 'boolean', description: 'Skip LLM enrichment' },
245
+ dry_run: { type: 'boolean', description: 'Preview what would be collected' },
246
+ },
247
+ },
248
+ },
249
+ {
250
+ name: 'analyzthis_retrieve',
251
+ description: 'Retrieve CSV reference data. Query the 28-file reference library (colors, styles, typography, UX guidelines, charts, products, landing patterns, 16 stack guides). Returns filtered, citation-ready rows.',
252
+ inputSchema: {
253
+ type: 'object',
254
+ properties: {
255
+ file: { type: 'string', description: 'CSV filename (e.g., "styles.csv", "colors.csv", "stacks/nextjs.csv")' },
256
+ column: { type: 'string', description: 'Column to filter on (e.g., "Product Type", "Category", "Best For Tags")' },
257
+ keywords: { type: 'string', description: 'Comma-separated keywords to match (e.g., "saas,dashboard")' },
258
+ limit: { type: 'number', description: 'Max rows to return (default: 3)' },
259
+ },
260
+ required: ['file'],
261
+ },
262
+ },
263
+ {
264
+ name: 'analyzthis_session',
265
+ description: 'Read or write shared session state. The session holds persona outputs, routing decisions, synthesis results, metrics, and evolution data. Use to check what a previous run produced.',
266
+ inputSchema: {
267
+ type: 'object',
268
+ properties: {
269
+ action: { type: 'string', enum: ['show', 'init', 'reset', 'accept'], description: 'show: display current state; init: create fresh; reset: delete; accept: mark a persona output as accepted' },
270
+ persona: { type: 'string', description: 'Persona ID (for accept action)' },
271
+ reject: { type: 'boolean', description: 'Reject instead of accept (for accept action)' },
272
+ },
273
+ required: ['action'],
274
+ },
275
+ },
276
+ {
277
+ name: 'analyzthis_system_prompt',
278
+ description: 'Generate a consolidated system prompt for AI tools that do not support MCP. Produces a single text block containing all 8 personas, deliberation rules, premise-challenge logic, and output formats. Paste into Lovable, v0, Bolt, ChatGPT, or any tool\'s system instructions.',
279
+ inputSchema: {
280
+ type: 'object',
281
+ properties: {
282
+ mode: { type: 'string', enum: ['create', 'critique', 'both'], description: 'Which mode to include (default: both)' },
283
+ },
284
+ },
285
+ },
286
+ ];
287
+
288
+ // ── Tool handlers ────────────────────────────────────────────────────────
289
+
290
+ function handleToolCall(name, args) {
291
+ switch (name) {
292
+
293
+ // ── Individual personas ──
294
+ case 'analyzthis_arjun':
295
+ case 'analyzthis_meera':
296
+ case 'analyzthis_priya':
297
+ case 'analyzthis_zara':
298
+ case 'analyzthis_noor':
299
+ case 'analyzthis_anuj':
300
+ case 'analyzthis_raj':
301
+ case 'analyzthis_kavi':
302
+ return handleSinglePersona(name.replace('analyzthis_', ''), args);
303
+
304
+ // ── Combination passes ──
305
+ case 'analyzthis_ux_ideator':
306
+ return handleCombinationPass('ux-ideator', args);
307
+ case 'analyzthis_persona_orchestrator':
308
+ return handleCombinationPass('persona-orchestrator', args);
309
+ case 'analyzthis_design_critic':
310
+ return handleCombinationPass('design-critic', args);
311
+ case 'analyzthis_ux_story_gate':
312
+ return handleCombinationPass('ux-story-gate', args);
313
+ case 'analyzthis_design_director':
314
+ return handleCombinationPass('design-director', args);
315
+ case 'analyzthis_mood_board':
316
+ return handleMoodBoard(args);
317
+ case 'analyzthis_evolve_check':
318
+ return handleEvolveCheck(args);
319
+
320
+ // ── Utility ──
321
+ case 'analyzthis_collect':
322
+ return handleCollect(args);
323
+ case 'analyzthis_retrieve':
324
+ return handleRetrieve(args);
325
+ case 'analyzthis_session':
326
+ return handleSession(args);
327
+ case 'analyzthis_system_prompt':
328
+ return handleSystemPrompt(args);
329
+
330
+ default:
331
+ return { error: 'Unknown tool: ' + name };
332
+ }
333
+ }
334
+
335
+ function handleSinglePersona(personaId, args) {
336
+ var card = readCard(personaId);
337
+ if (!card) return { error: 'Persona not found: ' + personaId };
338
+
339
+ var task = args.task || '';
340
+ var context = args.context || '';
341
+ var extra = '';
342
+
343
+ // Persona-specific context
344
+ if (personaId === 'priya' && args.stack) extra = '\nStack: ' + args.stack;
345
+ if (personaId === 'zara' && args.surface_type) extra = '\nSurface type: ' + args.surface_type;
346
+ if (personaId === 'zara' && args.high_frequency) extra += '\nHigh-frequency surface: yes';
347
+ if (personaId === 'raj' && args.contested_dimensions) extra = '\nContested dimensions: ' + args.contested_dimensions.join(', ');
348
+
349
+ var prompt = 'You are activated as the ' + personaId + ' persona.\n\n' +
350
+ card + '\n\n' +
351
+ '## Task\n' + task + '\n' +
352
+ (context ? '\n## Context\n' + context : '') +
353
+ extra + '\n\n' +
354
+ 'Produce your output following the schema in your card above. ' +
355
+ 'Be specific — cite components, zones, and fixes. ' +
356
+ 'Do not give generic advice.';
357
+
358
+ return { prompt: prompt, persona: personaId, task: task };
359
+ }
360
+
361
+ function handleCombinationPass(skillId, args) {
362
+ var skill = readSkill(skillId);
363
+ if (!skill) return { error: 'Skill not found: ' + skillId };
364
+
365
+ var task = args.task || '';
366
+ var context = '';
367
+
368
+ // Build context from args
369
+ var contextParts = [];
370
+ for (var key in args) {
371
+ if (key === 'task') continue;
372
+ if (args[key] !== undefined && args[key] !== null && args[key] !== '') {
373
+ var label = key.replace(/_/g, ' ').replace(/\b\w/g, function(c) { return c.toUpperCase(); });
374
+ contextParts.push(label + ': ' + (typeof args[key] === 'boolean' ? (args[key] ? 'yes' : 'no') : args[key]));
375
+ }
376
+ }
377
+ if (contextParts.length) context = contextParts.join('\n');
378
+
379
+ var prompt = 'Follow the workflow defined in this skill.\n\n' +
380
+ '# Skill: ' + skillId + '\n\n' +
381
+ skill + '\n\n' +
382
+ '## Task\n' + task + '\n' +
383
+ (context ? '\n## Context\n' + context : '') + '\n\n' +
384
+ 'Run all phases sequentially. Challenge the premise first. ' +
385
+ 'Resolve any persona disagreements explicitly — pick a winner with a forward path. ' +
386
+ 'Produce the final output in the format specified by the skill.';
387
+
388
+ return { prompt: prompt, skill: skillId, task: task };
389
+ }
390
+
391
+ function handleMoodBoard(args) {
392
+ var skill = readSkill('mood-board');
393
+ var task = args.task || '';
394
+ var urls = (args.urls || []).join('\n');
395
+ var prompt = 'Build a mood board.\n\n' +
396
+ (skill ? skill.slice(0, 3000) : '') + '\n\n' +
397
+ '## Task\n' + task + '\n' +
398
+ (urls ? '\n## Reference URLs\n' + urls : '') + '\n\n' +
399
+ 'Collect references, tag them, and run team deliberation using the UX Honeycomb matrix.';
400
+
401
+ return { prompt: prompt, task: task };
402
+ }
403
+
404
+ function handleEvolveCheck(args) {
405
+ var action = args.action || 'metrics';
406
+ try {
407
+ if (action === 'metrics' || action === 'ready') {
408
+ var em = require('./evolution-metrics');
409
+ var sessionMod = require('./session');
410
+ var projectId = sessionMod.getProjectId();
411
+ if (action === 'metrics') {
412
+ var m = em.computeEvolutionMetrics(projectId);
413
+ return { result: em.formatEvolutionSummary(m), action: 'metrics' };
414
+ } else {
415
+ var ready = em.checkEvolutionReady(projectId);
416
+ return { result: em.formatEvolutionSummary(ready.metrics), ready: ready.ready, reason: ready.reason };
417
+ }
418
+ }
419
+ if (action === 'extract') {
420
+ var evolve = require('./evolve');
421
+ return { result: 'Run: npx analyzthis_design evolve --extract --dry-run to propose patches', action: 'extract' };
422
+ }
423
+ if (action === 'apply') {
424
+ return { result: 'Run: npx analyzthis_design evolve --apply ' + (args.patch_id || '<patchId>'), action: 'apply' };
425
+ }
426
+ } catch (e) {
427
+ return { error: e.message };
428
+ }
429
+ return { error: 'Unknown evolve action: ' + action };
430
+ }
431
+
432
+ function handleCollect(args) {
433
+ var parts = ['npx analyzthis_design collect'];
434
+ if (args.vault_path) parts.push('--vault "' + args.vault_path + '"');
435
+ if (args.no_web) parts.push('--no-web');
436
+ if (args.no_enrich) parts.push('--no-enrich');
437
+ if (args.dry_run) parts.push('--dry-run');
438
+ return { command: parts.join(' '), note: 'Run this in your project directory to scan the codebase and build the knowledge bank.' };
439
+ }
440
+
441
+ function handleRetrieve(args) {
442
+ try {
443
+ var retrieve = require('./retrieve');
444
+ var file = args.file;
445
+ if (!file) return { error: 'file is required' };
446
+ var filters = [];
447
+ if (args.column && args.keywords) {
448
+ filters.push({ column: args.column, anyOf: args.keywords.split(',').map(function(s) { return s.trim(); }) });
449
+ }
450
+ var limit = args.limit || 3;
451
+ var result = retrieve.retrieve({ file: file, filters: filters, limit: limit });
452
+ if (!result.rows.length) return { result: 'No rows matched in ' + file };
453
+ var lines = [result.rows.length + ' matching row(s) in ' + file + (result.cacheHit ? ' (cache hit)' : '') + ':'];
454
+ for (var i = 0; i < result.rows.length; i++) {
455
+ lines.push('Row ' + result.rows[i].__no + ':');
456
+ for (var k in result.rows[i]) {
457
+ if (k.startsWith('__') || !result.rows[i][k]) continue;
458
+ lines.push(' ' + k + ': ' + result.rows[i][k]);
459
+ }
460
+ }
461
+ return { result: lines.join('\n') };
462
+ } catch (e) {
463
+ return { error: e.message };
464
+ }
465
+ }
466
+
467
+ function handleSession(args) {
468
+ try {
469
+ var session = require('./session');
470
+ var action = args.action;
471
+ if (action === 'show') {
472
+ var state = session.show();
473
+ if (!state) return { result: 'No session exists. Run npx analyzthis_design session init.' };
474
+ return { result: JSON.stringify(state, null, 2).slice(0, 4000) };
475
+ }
476
+ if (action === 'init') {
477
+ session.init();
478
+ return { result: 'Session initialized.' };
479
+ }
480
+ if (action === 'reset') {
481
+ session.reset();
482
+ return { result: 'Session reset.' };
483
+ }
484
+ if (action === 'accept') {
485
+ if (!args.persona) return { error: 'persona is required for accept' };
486
+ var result = session.markAccepted({ persona: args.persona, accepted: !args.reject });
487
+ return { result: JSON.stringify(result) };
488
+ }
489
+ } catch (e) {
490
+ return { error: e.message };
491
+ }
492
+ return { error: 'Unknown session action: ' + args.action };
493
+ }
494
+
495
+ function handleSystemPrompt(args) {
496
+ try {
497
+ var builder = require('./system-prompt');
498
+ var mode = args.mode || 'both';
499
+ var prompt = builder.buildPrompt({ mode: mode });
500
+ return { result: prompt };
501
+ } catch (e) {
502
+ // Fallback: generate inline
503
+ return { result: 'System prompt generator not available. Run: npx analyzthis_design system-prompt --mode ' + (args.mode || 'both') };
504
+ }
505
+ }
506
+
507
+ // ── Helpers ──
508
+
509
+ function readCard(id) {
510
+ var p = path.join(PACKAGE_ROOT, 'agents', 'cards', id + '.md');
511
+ if (!fs.existsSync(p)) return null;
512
+ return fs.readFileSync(p, 'utf8');
513
+ }
514
+
515
+ function readSkill(id) {
516
+ var p = path.join(PACKAGE_ROOT, 'skills', id, 'SKILL.md');
517
+ if (!fs.existsSync(p)) return null;
518
+ return fs.readFileSync(p, 'utf8');
519
+ }
520
+
521
+ // ── MCP config writer ──
522
+
523
+ function writeMcpConfig(target) {
524
+ var serverCmd = 'npx';
525
+ var serverArgs = ['analyzthis_design', 'mcp'];
526
+ var home = os.homedir();
527
+
528
+ if (target === 'cursor') {
529
+ var cursorConfigPath = path.join(home, '.cursor', 'mcp.json');
530
+ var cursorDir = path.dirname(cursorConfigPath);
531
+ if (!fs.existsSync(cursorDir)) fs.mkdirSync(cursorDir, { recursive: true });
532
+ var cursorConfig = {};
533
+ if (fs.existsSync(cursorConfigPath)) {
534
+ try { cursorConfig = JSON.parse(fs.readFileSync(cursorConfigPath, 'utf8')); } catch (e) {}
535
+ }
536
+ cursorConfig.mcpServers = cursorConfig.mcpServers || {};
537
+ cursorConfig.mcpServers['analyzthis_design'] = {
538
+ command: serverCmd,
539
+ args: serverArgs,
540
+ };
541
+ fs.writeFileSync(cursorConfigPath, JSON.stringify(cursorConfig, null, 2));
542
+ console.log('Cursor MCP config written to ' + cursorConfigPath);
543
+ console.log('Restart Cursor to activate.\n');
544
+ return true;
545
+ }
546
+
547
+ if (target === 'claude') {
548
+ var claudeConfigPath;
549
+ if (process.platform === 'darwin') {
550
+ claudeConfigPath = path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
551
+ } else if (process.platform === 'win32') {
552
+ claudeConfigPath = path.join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
553
+ } else {
554
+ claudeConfigPath = path.join(home, '.config', 'Claude', 'claude_desktop_config.json');
555
+ }
556
+ var claudeDir = path.dirname(claudeConfigPath);
557
+ if (!fs.existsSync(claudeDir)) fs.mkdirSync(claudeDir, { recursive: true });
558
+ var claudeConfig = {};
559
+ if (fs.existsSync(claudeConfigPath)) {
560
+ try { claudeConfig = JSON.parse(fs.readFileSync(claudeConfigPath, 'utf8')); } catch (e) {}
561
+ }
562
+ claudeConfig.mcpServers = claudeConfig.mcpServers || {};
563
+ claudeConfig.mcpServers['analyzthis_design'] = {
564
+ command: serverCmd,
565
+ args: serverArgs,
566
+ };
567
+ fs.writeFileSync(claudeConfigPath, JSON.stringify(claudeConfig, null, 2));
568
+ console.log('Claude Desktop MCP config written to ' + claudeConfigPath);
569
+ console.log('Restart Claude Desktop to activate.\n');
570
+ return true;
571
+ }
572
+
573
+ if (target === 'windsurf') {
574
+ var windsurfConfigPath = path.join(home, '.codeium', 'windsurf', 'mcp_config.json');
575
+ var windsurfDir = path.dirname(windsurfConfigPath);
576
+ if (!fs.existsSync(windsurfDir)) fs.mkdirSync(windsurfDir, { recursive: true });
577
+ var windsurfConfig = {};
578
+ if (fs.existsSync(windsurfConfigPath)) {
579
+ try { windsurfConfig = JSON.parse(fs.readFileSync(windsurfConfigPath, 'utf8')); } catch (e) {}
580
+ }
581
+ windsurfConfig.mcpServers = windsurfConfig.mcpServers || {};
582
+ windsurfConfig.mcpServers['analyzthis_design'] = {
583
+ command: serverCmd,
584
+ args: serverArgs,
585
+ };
586
+ fs.writeFileSync(windsurfConfigPath, JSON.stringify(windsurfConfig, null, 2));
587
+ console.log('Windsurf MCP config written to ' + windsurfConfigPath);
588
+ console.log('Restart Windsurf to activate.\n');
589
+ return true;
590
+ }
591
+
592
+ console.log('Manual config for ' + target + ':');
593
+ console.log(JSON.stringify({
594
+ mcpServers: {
595
+ 'analyzthis_design': {
596
+ command: serverCmd,
597
+ args: serverArgs,
598
+ }
599
+ }
600
+ }, null, 2));
601
+ console.log('\nPaste this into your tool\'s MCP server configuration.\n');
602
+ return true;
603
+ }
604
+
605
+ // ── MCP protocol (JSON-RPC 2.0 over stdio) ────────────────────────────────
606
+
607
+ function startStdioServer() {
608
+ var rl = readline.createInterface({ input: process.stdin, terminal: false });
609
+ var initialized = false;
610
+
611
+ function send(msg) {
612
+ process.stdout.write(JSON.stringify(msg) + '\n');
613
+ }
614
+
615
+ function handleMessage(msg) {
616
+ if (!msg || !msg.jsonrpc) return;
617
+ var id = msg.id;
618
+ var method = msg.method;
619
+ var params = msg.params || {};
620
+
621
+ // Initialize
622
+ if (method === 'initialize') {
623
+ initialized = true;
624
+ send({
625
+ jsonrpc: '2.0',
626
+ id: id,
627
+ result: {
628
+ protocolVersion: '2024-11-05',
629
+ capabilities: {
630
+ tools: {},
631
+ },
632
+ serverInfo: {
633
+ name: 'analyzthis_design',
634
+ version: VERSION,
635
+ },
636
+ },
637
+ });
638
+ return;
639
+ }
640
+
641
+ // Initialized notification
642
+ if (method === 'notifications/initialized') {
643
+ return;
644
+ }
645
+
646
+ // Tools list
647
+ if (method === 'tools/list') {
648
+ send({
649
+ jsonrpc: '2.0',
650
+ id: id,
651
+ result: {
652
+ tools: TOOLS.map(function(t) {
653
+ return {
654
+ name: t.name,
655
+ description: t.description,
656
+ inputSchema: t.inputSchema,
657
+ };
658
+ }),
659
+ },
660
+ });
661
+ return;
662
+ }
663
+
664
+ // Tool call
665
+ if (method === 'tools/call') {
666
+ var toolName = params.name;
667
+ var toolArgs = params.arguments || {};
668
+ var result = handleToolCall(toolName, toolArgs);
669
+
670
+ if (result.error) {
671
+ send({
672
+ jsonrpc: '2.0',
673
+ id: id,
674
+ error: { code: -32603, message: result.error },
675
+ });
676
+ } else {
677
+ var text = result.prompt || result.result || result.command || JSON.stringify(result, null, 2);
678
+ send({
679
+ jsonrpc: '2.0',
680
+ id: id,
681
+ result: {
682
+ content: [{ type: 'text', text: String(text) }],
683
+ },
684
+ });
685
+ }
686
+ return;
687
+ }
688
+
689
+ // Unknown method
690
+ if (id !== undefined) {
691
+ send({
692
+ jsonrpc: '2.0',
693
+ id: id,
694
+ error: { code: -32601, message: 'Method not found: ' + method },
695
+ });
696
+ }
697
+ }
698
+
699
+ rl.on('line', function(line) {
700
+ try {
701
+ var msg = JSON.parse(line);
702
+ handleMessage(msg);
703
+ } catch (e) {
704
+ if (process.env.MCP_DEBUG) {
705
+ process.stderr.write('Parse error: ' + e.message + '\n');
706
+ }
707
+ }
708
+ });
709
+
710
+ rl.on('close', function() {
711
+ process.exit(0);
712
+ });
713
+
714
+ if (process.env.MCP_DEBUG) {
715
+ process.stderr.write('analyzthis_design MCP server v' + VERSION + ' running on stdio\n');
716
+ process.stderr.write('Tools: ' + TOOLS.length + ' available\n');
717
+ }
718
+ }
719
+
720
+ // ── Entry point ──
721
+
722
+ function main() {
723
+ var args = process.argv.slice(2);
724
+
725
+ // --configure <target>
726
+ var configureIdx = args.indexOf('--configure');
727
+ if (configureIdx !== -1 && args[configureIdx + 1]) {
728
+ writeMcpConfig(args[configureIdx + 1]);
729
+ return;
730
+ }
731
+
732
+ // Default: start stdio server
733
+ startStdioServer();
734
+ }
735
+
736
+ if (require.main === module) {
737
+ main();
738
+ }
739
+
740
+ module.exports = {
741
+ TOOLS: TOOLS,
742
+ handleToolCall: handleToolCall,
743
+ startStdioServer: startStdioServer,
744
+ writeMcpConfig: writeMcpConfig,
745
+ };