design-system-selector 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,6 +18,22 @@ brew install design-system-selector
18
18
 
19
19
  Requires Node.js 20 or later.
20
20
 
21
+ ## AI Matching
22
+
23
+ AI matching is provided through the hosted Design System Selector API proxy.
24
+
25
+ - Users do **not** need to configure `ANTHROPIC_API_KEY`
26
+ - The CLI sends questionnaire preferences to the backend and receives ranked results
27
+ - If the API is unavailable, the CLI automatically falls back to static matching
28
+
29
+ For local backend testing, maintainers can override the API base URL:
30
+
31
+ ```sh
32
+ DESIGN_SYSTEM_SELECTOR_API_URL=http://localhost:8787 npx design-system-selector
33
+ ```
34
+
35
+ Backend deployment guide: [`docs/vercel-backend.md`](docs/vercel-backend.md)
36
+
21
37
  ## How It Works
22
38
 
23
39
  The CLI asks you 5 questions about your project:
package/dist/cli.js CHANGED
@@ -402,91 +402,43 @@ function match(systems2, prefs) {
402
402
  }
403
403
 
404
404
  // src/agent/client.ts
405
- import Anthropic from "@anthropic-ai/sdk";
406
- function isAgentAvailable() {
407
- const key = process.env["ANTHROPIC_API_KEY"];
408
- return typeof key === "string" && key.length > 0;
409
- }
410
- function createClient() {
411
- if (!isAgentAvailable()) {
412
- throw new Error(
413
- "ANTHROPIC_API_KEY environment variable is not set. AI matching is unavailable."
414
- );
405
+ var DEFAULT_AGENT_API_URL = "https://api.thelostbygass.com";
406
+ var AGENT_MATCH_PATH = "/v1/match";
407
+ var REQUEST_TIMEOUT_MS = 15e3;
408
+ function normalizeBaseUrl(raw) {
409
+ return raw.replace(/\/+$/, "");
410
+ }
411
+ function getAgentApiUrl() {
412
+ const configured = process.env["DESIGN_SYSTEM_SELECTOR_API_URL"];
413
+ if (typeof configured === "string" && configured.trim().length > 0) {
414
+ return normalizeBaseUrl(configured.trim());
415
+ }
416
+ return DEFAULT_AGENT_API_URL;
417
+ }
418
+ function buildEndpoint() {
419
+ return `${getAgentApiUrl()}${AGENT_MATCH_PATH}`;
420
+ }
421
+ async function requestAgentMatch(preferences) {
422
+ const payload = { preferences };
423
+ const response = await fetch(buildEndpoint(), {
424
+ method: "POST",
425
+ headers: {
426
+ "Content-Type": "application/json",
427
+ Accept: "application/json"
428
+ },
429
+ body: JSON.stringify(payload),
430
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
431
+ });
432
+ if (!response.ok) {
433
+ const details = (await response.text()).trim();
434
+ const suffix = details ? `: ${details}` : "";
435
+ throw new Error(`AI API request failed (${response.status})${suffix}`);
415
436
  }
416
- return new Anthropic();
417
- }
418
-
419
- // src/agent/prompt.ts
420
- function buildSystemPrompt() {
421
- return `You are a design system advisor. Given a set of design systems and user preferences, score and rank the systems to find the best matches.
422
-
423
- ## Scoring Dimensions (total 100 points)
424
- - accessibility (22): Match accessibility score and EU compliance to user need
425
- - designPhilosophy (18): Match visual style to system philosophy
426
- - customization (18): Match opinion level to customization depth
427
- - learningCurve (17): Match experience level to learning curve
428
- - projectType (15): Match project type to system strengths
429
- - color (10): Match color preference to system color strengths
430
-
431
- ## Accessibility Hard Filter
432
- Before scoring, exclude systems that fail the accessibility filter:
433
- - "critical" need \u2192 ONLY systems with accessibilityScore "AAA" AND euCompliance "full"
434
- - "important" need \u2192 ONLY systems with accessibilityScore "AAA" or "AA"
435
- - "standard" need \u2192 all systems pass
436
-
437
- ## Framework Hard Filter
438
- Before scoring, exclude systems that don't support the user's framework:
439
- - If framework is "any" \u2192 all systems pass
440
- - Otherwise \u2192 ONLY systems whose frameworks array includes the user's framework
441
-
442
- ## Output Format
443
- Return ONLY valid JSON (no markdown, no explanation) matching this exact shape:
444
- {
445
- "primary": { "systemId": string, "score": number (0-100), "matchedDimensions": number (0-6), "reasons": string[] },
446
- "alternatives": [
447
- { "systemId": string, "score": number (0-100), "matchedDimensions": number (0-6), "reasons": string[] },
448
- { "systemId": string, "score": number (0-100), "matchedDimensions": number (0-6), "reasons": string[] }
449
- ],
450
- "relaxed": boolean
451
- }
452
-
453
- Rules:
454
- - primary is the best match, alternatives are the next two best
455
- - reasons should be 2-4 concise, context-aware explanations per system
456
- - relaxed is true if primary matchedDimensions < 4
457
- - systemId must be one of the provided system IDs
458
- - score is 0-100, matchedDimensions is 0-6`;
459
- }
460
- function compressSystem(system) {
461
- return {
462
- id: system.id,
463
- name: system.name,
464
- philosophy: system.designPhilosophy.join(","),
465
- frameworks: system.frameworks.join(","),
466
- a11y: system.accessibilityScore,
467
- customization: system.customizationDepth,
468
- learning: system.learningCurve,
469
- colors: system.colorStrengths.join(","),
470
- projects: system.projectStrengths.join(","),
471
- eu: system.euCompliance,
472
- tradeoffs: system.tradeoffs
473
- };
474
- }
475
- function buildUserMessage(systems2, prefs) {
476
- const compressed = systems2.map(compressSystem);
477
- return `## Available Design Systems
478
- ${compressed.map((s2) => `- ${s2.id}: ${s2.name} | philosophy=${s2.philosophy} | frameworks=${s2.frameworks} | a11y=${s2.a11y} | eu=${s2.eu} | customization=${s2.customization} | learning=${s2.learning} | colors=${s2.colors} | projects=${s2.projects} | tradeoffs="${s2.tradeoffs}"`).join("\n")}
479
-
480
- ## User Preferences
481
- - visualStyle: ${prefs.visualStyle}
482
- - accessibilityNeed: ${prefs.accessibilityNeed}
483
- - opinionLevel: ${prefs.opinionLevel}
484
- - experienceLevel: ${prefs.experienceLevel}
485
- - colorPreference: ${prefs.colorPreference}
486
- - projectType: ${prefs.projectType}
487
- - framework: ${prefs.framework}
488
-
489
- Score and rank these systems for this user. Return JSON only.`;
437
+ const json = await response.json();
438
+ if (typeof json !== "object" || json === null) {
439
+ throw new Error("AI API response is not a JSON object");
440
+ }
441
+ return json;
490
442
  }
491
443
 
492
444
  // src/agent/parser.ts
@@ -581,24 +533,8 @@ function parseAgentResponse(raw, systems2) {
581
533
 
582
534
  // src/agent/matcher.ts
583
535
  async function agentMatch(systems2, prefs) {
584
- const client = createClient();
585
- const response = await client.messages.create({
586
- model: "claude-haiku-4-5-20251001",
587
- max_tokens: 1024,
588
- temperature: 0,
589
- system: buildSystemPrompt(),
590
- messages: [
591
- {
592
- role: "user",
593
- content: buildUserMessage(systems2, prefs)
594
- }
595
- ]
596
- });
597
- const textBlock = response.content.find((block) => block.type === "text");
598
- if (!textBlock || textBlock.type !== "text") {
599
- throw new Error("No text content in AI response");
600
- }
601
- return parseAgentResponse(textBlock.text, systems2);
536
+ const rawResponse = await requestAgentMatch(prefs);
537
+ return parseAgentResponse(JSON.stringify(rawResponse), systems2);
602
538
  }
603
539
 
604
540
  // src/output.ts
@@ -3773,26 +3709,20 @@ async function main() {
3773
3709
  }
3774
3710
  const prefs = await runQuestionnaire();
3775
3711
  let result;
3776
- if (isAgentAvailable()) {
3712
+ if (!mcpOnly) {
3713
+ console.log(chalk5.dim("\n Analyzing with AI...\n"));
3714
+ }
3715
+ try {
3716
+ result = await agentMatch(systems, prefs);
3717
+ } catch (err) {
3777
3718
  if (!mcpOnly) {
3778
- console.log(chalk5.dim("\n Analyzing with AI...\n"));
3779
- }
3780
- try {
3781
- result = await agentMatch(systems, prefs);
3782
- } catch (err) {
3783
- if (!mcpOnly) {
3784
- console.log(
3785
- chalk5.yellow(
3786
- ` AI matching failed (${err instanceof Error ? err.message : "unknown error"}), using static analysis...
3719
+ console.log(
3720
+ chalk5.yellow(
3721
+ ` AI matching unavailable (${err instanceof Error ? err.message : "unknown error"}), using static analysis...
3787
3722
  `
3788
- )
3789
- );
3790
- }
3791
- result = match(systems, prefs);
3792
- }
3793
- } else {
3794
- if (!mcpOnly) {
3795
- console.log(chalk5.dim("\n Analyzing your preferences...\n"));
3723
+ )
3724
+ );
3725
+ console.log(chalk5.dim(" Analyzing your preferences...\n"));
3796
3726
  }
3797
3727
  result = match(systems, prefs);
3798
3728
  }
package/dist/index.js CHANGED
@@ -400,91 +400,43 @@ function match(systems2, prefs) {
400
400
  }
401
401
 
402
402
  // src/agent/client.ts
403
- import Anthropic from "@anthropic-ai/sdk";
404
- function isAgentAvailable() {
405
- const key = process.env["ANTHROPIC_API_KEY"];
406
- return typeof key === "string" && key.length > 0;
407
- }
408
- function createClient() {
409
- if (!isAgentAvailable()) {
410
- throw new Error(
411
- "ANTHROPIC_API_KEY environment variable is not set. AI matching is unavailable."
412
- );
403
+ var DEFAULT_AGENT_API_URL = "https://api.thelostbygass.com";
404
+ var AGENT_MATCH_PATH = "/v1/match";
405
+ var REQUEST_TIMEOUT_MS = 15e3;
406
+ function normalizeBaseUrl(raw) {
407
+ return raw.replace(/\/+$/, "");
408
+ }
409
+ function getAgentApiUrl() {
410
+ const configured = process.env["DESIGN_SYSTEM_SELECTOR_API_URL"];
411
+ if (typeof configured === "string" && configured.trim().length > 0) {
412
+ return normalizeBaseUrl(configured.trim());
413
+ }
414
+ return DEFAULT_AGENT_API_URL;
415
+ }
416
+ function buildEndpoint() {
417
+ return `${getAgentApiUrl()}${AGENT_MATCH_PATH}`;
418
+ }
419
+ async function requestAgentMatch(preferences) {
420
+ const payload = { preferences };
421
+ const response = await fetch(buildEndpoint(), {
422
+ method: "POST",
423
+ headers: {
424
+ "Content-Type": "application/json",
425
+ Accept: "application/json"
426
+ },
427
+ body: JSON.stringify(payload),
428
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
429
+ });
430
+ if (!response.ok) {
431
+ const details = (await response.text()).trim();
432
+ const suffix = details ? `: ${details}` : "";
433
+ throw new Error(`AI API request failed (${response.status})${suffix}`);
413
434
  }
414
- return new Anthropic();
415
- }
416
-
417
- // src/agent/prompt.ts
418
- function buildSystemPrompt() {
419
- return `You are a design system advisor. Given a set of design systems and user preferences, score and rank the systems to find the best matches.
420
-
421
- ## Scoring Dimensions (total 100 points)
422
- - accessibility (22): Match accessibility score and EU compliance to user need
423
- - designPhilosophy (18): Match visual style to system philosophy
424
- - customization (18): Match opinion level to customization depth
425
- - learningCurve (17): Match experience level to learning curve
426
- - projectType (15): Match project type to system strengths
427
- - color (10): Match color preference to system color strengths
428
-
429
- ## Accessibility Hard Filter
430
- Before scoring, exclude systems that fail the accessibility filter:
431
- - "critical" need \u2192 ONLY systems with accessibilityScore "AAA" AND euCompliance "full"
432
- - "important" need \u2192 ONLY systems with accessibilityScore "AAA" or "AA"
433
- - "standard" need \u2192 all systems pass
434
-
435
- ## Framework Hard Filter
436
- Before scoring, exclude systems that don't support the user's framework:
437
- - If framework is "any" \u2192 all systems pass
438
- - Otherwise \u2192 ONLY systems whose frameworks array includes the user's framework
439
-
440
- ## Output Format
441
- Return ONLY valid JSON (no markdown, no explanation) matching this exact shape:
442
- {
443
- "primary": { "systemId": string, "score": number (0-100), "matchedDimensions": number (0-6), "reasons": string[] },
444
- "alternatives": [
445
- { "systemId": string, "score": number (0-100), "matchedDimensions": number (0-6), "reasons": string[] },
446
- { "systemId": string, "score": number (0-100), "matchedDimensions": number (0-6), "reasons": string[] }
447
- ],
448
- "relaxed": boolean
449
- }
450
-
451
- Rules:
452
- - primary is the best match, alternatives are the next two best
453
- - reasons should be 2-4 concise, context-aware explanations per system
454
- - relaxed is true if primary matchedDimensions < 4
455
- - systemId must be one of the provided system IDs
456
- - score is 0-100, matchedDimensions is 0-6`;
457
- }
458
- function compressSystem(system) {
459
- return {
460
- id: system.id,
461
- name: system.name,
462
- philosophy: system.designPhilosophy.join(","),
463
- frameworks: system.frameworks.join(","),
464
- a11y: system.accessibilityScore,
465
- customization: system.customizationDepth,
466
- learning: system.learningCurve,
467
- colors: system.colorStrengths.join(","),
468
- projects: system.projectStrengths.join(","),
469
- eu: system.euCompliance,
470
- tradeoffs: system.tradeoffs
471
- };
472
- }
473
- function buildUserMessage(systems2, prefs) {
474
- const compressed = systems2.map(compressSystem);
475
- return `## Available Design Systems
476
- ${compressed.map((s2) => `- ${s2.id}: ${s2.name} | philosophy=${s2.philosophy} | frameworks=${s2.frameworks} | a11y=${s2.a11y} | eu=${s2.eu} | customization=${s2.customization} | learning=${s2.learning} | colors=${s2.colors} | projects=${s2.projects} | tradeoffs="${s2.tradeoffs}"`).join("\n")}
477
-
478
- ## User Preferences
479
- - visualStyle: ${prefs.visualStyle}
480
- - accessibilityNeed: ${prefs.accessibilityNeed}
481
- - opinionLevel: ${prefs.opinionLevel}
482
- - experienceLevel: ${prefs.experienceLevel}
483
- - colorPreference: ${prefs.colorPreference}
484
- - projectType: ${prefs.projectType}
485
- - framework: ${prefs.framework}
486
-
487
- Score and rank these systems for this user. Return JSON only.`;
435
+ const json = await response.json();
436
+ if (typeof json !== "object" || json === null) {
437
+ throw new Error("AI API response is not a JSON object");
438
+ }
439
+ return json;
488
440
  }
489
441
 
490
442
  // src/agent/parser.ts
@@ -579,24 +531,8 @@ function parseAgentResponse(raw, systems2) {
579
531
 
580
532
  // src/agent/matcher.ts
581
533
  async function agentMatch(systems2, prefs) {
582
- const client = createClient();
583
- const response = await client.messages.create({
584
- model: "claude-haiku-4-5-20251001",
585
- max_tokens: 1024,
586
- temperature: 0,
587
- system: buildSystemPrompt(),
588
- messages: [
589
- {
590
- role: "user",
591
- content: buildUserMessage(systems2, prefs)
592
- }
593
- ]
594
- });
595
- const textBlock = response.content.find((block) => block.type === "text");
596
- if (!textBlock || textBlock.type !== "text") {
597
- throw new Error("No text content in AI response");
598
- }
599
- return parseAgentResponse(textBlock.text, systems2);
534
+ const rawResponse = await requestAgentMatch(prefs);
535
+ return parseAgentResponse(JSON.stringify(rawResponse), systems2);
600
536
  }
601
537
 
602
538
  // src/output.ts
@@ -3771,26 +3707,20 @@ async function main() {
3771
3707
  }
3772
3708
  const prefs = await runQuestionnaire();
3773
3709
  let result;
3774
- if (isAgentAvailable()) {
3710
+ if (!mcpOnly) {
3711
+ console.log(chalk5.dim("\n Analyzing with AI...\n"));
3712
+ }
3713
+ try {
3714
+ result = await agentMatch(systems, prefs);
3715
+ } catch (err) {
3775
3716
  if (!mcpOnly) {
3776
- console.log(chalk5.dim("\n Analyzing with AI...\n"));
3777
- }
3778
- try {
3779
- result = await agentMatch(systems, prefs);
3780
- } catch (err) {
3781
- if (!mcpOnly) {
3782
- console.log(
3783
- chalk5.yellow(
3784
- ` AI matching failed (${err instanceof Error ? err.message : "unknown error"}), using static analysis...
3717
+ console.log(
3718
+ chalk5.yellow(
3719
+ ` AI matching unavailable (${err instanceof Error ? err.message : "unknown error"}), using static analysis...
3785
3720
  `
3786
- )
3787
- );
3788
- }
3789
- result = match(systems, prefs);
3790
- }
3791
- } else {
3792
- if (!mcpOnly) {
3793
- console.log(chalk5.dim("\n Analyzing your preferences...\n"));
3721
+ )
3722
+ );
3723
+ console.log(chalk5.dim(" Analyzing your preferences...\n"));
3794
3724
  }
3795
3725
  result = match(systems, prefs);
3796
3726
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "design-system-selector",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "CLI tool that recommends alternative design systems based on your project needs",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,6 +30,7 @@
30
30
  "homepage": "https://github.com/TheLostByGass/design-system-selector#readme",
31
31
  "scripts": {
32
32
  "dev": "tsx --env-file=.env src/index.ts",
33
+ "vercel-build": "echo 'No build step required for Vercel API deployment'",
33
34
  "lint": "eslint src tests",
34
35
  "format:check": "prettier --check .",
35
36
  "typecheck": "tsc --noEmit",
@@ -47,16 +48,15 @@
47
48
  ],
48
49
  "license": "MIT",
49
50
  "dependencies": {
50
- "@anthropic-ai/sdk": "^0.77.0",
51
51
  "@inquirer/prompts": "^7.0.0",
52
52
  "chalk": "^5.3.0",
53
53
  "ora": "^9.3.0"
54
54
  },
55
55
  "devDependencies": {
56
- "dotenv": "^17.3.1",
57
56
  "@eslint/js": "^10.0.1",
58
57
  "@modelcontextprotocol/sdk": "^1.27.1",
59
58
  "@types/node": "^22.0.0",
59
+ "dotenv": "^17.3.1",
60
60
  "eslint": "^10.0.0",
61
61
  "prettier": "^3.8.1",
62
62
  "tsup": "^8.0.0",