@hileeon/mcc 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.
Files changed (138) hide show
  1. package/.claude/CLAUDE.md +204 -0
  2. package/.claude/agents/.gitkeep +0 -0
  3. package/.claude/settings.json +9 -0
  4. package/.claude/skills/.gitkeep +0 -0
  5. package/README.md +127 -0
  6. package/dist/accounts/instance-manager.d.ts +11 -0
  7. package/dist/accounts/instance-manager.d.ts.map +1 -0
  8. package/dist/accounts/instance-manager.js +89 -0
  9. package/dist/accounts/instance-manager.js.map +1 -0
  10. package/dist/accounts/shared-manager.d.ts +25 -0
  11. package/dist/accounts/shared-manager.d.ts.map +1 -0
  12. package/dist/accounts/shared-manager.js +186 -0
  13. package/dist/accounts/shared-manager.js.map +1 -0
  14. package/dist/accounts/store.d.ts +30 -0
  15. package/dist/accounts/store.d.ts.map +1 -0
  16. package/dist/accounts/store.js +128 -0
  17. package/dist/accounts/store.js.map +1 -0
  18. package/dist/core/model-router.d.ts +30 -0
  19. package/dist/core/model-router.d.ts.map +1 -0
  20. package/dist/core/model-router.js +64 -0
  21. package/dist/core/model-router.js.map +1 -0
  22. package/dist/dashboard-server.d.ts +5 -0
  23. package/dist/dashboard-server.d.ts.map +1 -0
  24. package/dist/dashboard-server.js +387 -0
  25. package/dist/dashboard-server.js.map +1 -0
  26. package/dist/mcc.d.ts +8 -0
  27. package/dist/mcc.d.ts.map +1 -0
  28. package/dist/mcc.js +474 -0
  29. package/dist/mcc.js.map +1 -0
  30. package/dist/mcp/external-registry.d.ts +24 -0
  31. package/dist/mcp/external-registry.d.ts.map +1 -0
  32. package/dist/mcp/external-registry.js +99 -0
  33. package/dist/mcp/external-registry.js.map +1 -0
  34. package/dist/mcp/installer.d.ts +31 -0
  35. package/dist/mcp/installer.d.ts.map +1 -0
  36. package/dist/mcp/installer.js +273 -0
  37. package/dist/mcp/installer.js.map +1 -0
  38. package/dist/mcp/mcp-config.d.ts +86 -0
  39. package/dist/mcp/mcp-config.d.ts.map +1 -0
  40. package/dist/mcp/mcp-config.js +178 -0
  41. package/dist/mcp/mcp-config.js.map +1 -0
  42. package/dist/mcp/registry.d.ts +23 -0
  43. package/dist/mcp/registry.d.ts.map +1 -0
  44. package/dist/mcp/registry.js +100 -0
  45. package/dist/mcp/registry.js.map +1 -0
  46. package/dist/proxy/proxy-daemon.d.ts +27 -0
  47. package/dist/proxy/proxy-daemon.d.ts.map +1 -0
  48. package/dist/proxy/proxy-daemon.js +192 -0
  49. package/dist/proxy/proxy-daemon.js.map +1 -0
  50. package/dist/proxy/proxy-entry.d.ts +11 -0
  51. package/dist/proxy/proxy-entry.d.ts.map +1 -0
  52. package/dist/proxy/proxy-entry.js +74 -0
  53. package/dist/proxy/proxy-entry.js.map +1 -0
  54. package/dist/proxy/proxy-paths.d.ts +27 -0
  55. package/dist/proxy/proxy-paths.d.ts.map +1 -0
  56. package/dist/proxy/proxy-paths.js +125 -0
  57. package/dist/proxy/proxy-paths.js.map +1 -0
  58. package/dist/proxy/proxy-server.d.ts +20 -0
  59. package/dist/proxy/proxy-server.d.ts.map +1 -0
  60. package/dist/proxy/proxy-server.js +280 -0
  61. package/dist/proxy/proxy-server.js.map +1 -0
  62. package/dist/proxy/upstream-url.d.ts +7 -0
  63. package/dist/proxy/upstream-url.d.ts.map +1 -0
  64. package/dist/proxy/upstream-url.js +38 -0
  65. package/dist/proxy/upstream-url.js.map +1 -0
  66. package/dist/shared/logger.d.ts +23 -0
  67. package/dist/shared/logger.d.ts.map +1 -0
  68. package/dist/shared/logger.js +184 -0
  69. package/dist/shared/logger.js.map +1 -0
  70. package/dist/shared/provider-preset-catalog.d.ts +41 -0
  71. package/dist/shared/provider-preset-catalog.d.ts.map +1 -0
  72. package/dist/shared/provider-preset-catalog.js +299 -0
  73. package/dist/shared/provider-preset-catalog.js.map +1 -0
  74. package/docs/decisions.md +33 -0
  75. package/docs/lessons.md +8 -0
  76. package/docs/product.md +37 -0
  77. package/lib/mcp/mcc-image-analysis-server.cjs +454 -0
  78. package/lib/mcp/mcc-websearch-server.cjs +339 -0
  79. package/lib/mcp-hooks/image-analysis-runtime.cjs +510 -0
  80. package/lib/mcp-hooks/image-analyzer-transformer.cjs +526 -0
  81. package/lib/mcp-hooks/websearch-transformer.cjs +1421 -0
  82. package/lib/proxy/config/config-loader-facade.js +24 -0
  83. package/lib/proxy/glmt/delta-accumulator.js +363 -0
  84. package/lib/proxy/glmt/glmt-transformer.js +204 -0
  85. package/lib/proxy/glmt/index.js +41 -0
  86. package/lib/proxy/glmt/locale-enforcer.js +69 -0
  87. package/lib/proxy/glmt/pipeline/content-transformer.js +162 -0
  88. package/lib/proxy/glmt/pipeline/index.js +20 -0
  89. package/lib/proxy/glmt/pipeline/request-transformer.js +116 -0
  90. package/lib/proxy/glmt/pipeline/response-builder.js +205 -0
  91. package/lib/proxy/glmt/pipeline/stream-parser.js +234 -0
  92. package/lib/proxy/glmt/pipeline/tool-call-handler.js +78 -0
  93. package/lib/proxy/glmt/pipeline/types.js +6 -0
  94. package/lib/proxy/glmt/reasoning-enforcer.js +151 -0
  95. package/lib/proxy/glmt/sse-parser.js +102 -0
  96. package/lib/proxy/services/logging.js +13 -0
  97. package/lib/proxy/transformers/request-transformer.js +452 -0
  98. package/lib/proxy/transformers/sse-stream-transformer.js +199 -0
  99. package/lib/shared/logger.cjs +138 -0
  100. package/package.json +35 -0
  101. package/src/accounts/instance-manager.ts +58 -0
  102. package/src/accounts/shared-manager.ts +154 -0
  103. package/src/accounts/store.ts +111 -0
  104. package/src/core/model-router.ts +82 -0
  105. package/src/dashboard-server.ts +407 -0
  106. package/src/mcc.ts +474 -0
  107. package/src/mcp/external-registry.ts +73 -0
  108. package/src/mcp/installer.ts +258 -0
  109. package/src/mcp/mcp-config.ts +168 -0
  110. package/src/mcp/registry.ts +89 -0
  111. package/src/proxy/proxy-daemon.ts +184 -0
  112. package/src/proxy/proxy-entry.ts +63 -0
  113. package/src/proxy/proxy-paths.ts +97 -0
  114. package/src/proxy/proxy-server.ts +278 -0
  115. package/src/proxy/upstream-url.ts +38 -0
  116. package/src/shared/logger.ts +140 -0
  117. package/src/shared/provider-preset-catalog.ts +340 -0
  118. package/tsconfig.json +33 -0
  119. package/ui/.prettierrc +9 -0
  120. package/ui/index.html +12 -0
  121. package/ui/package.json +33 -0
  122. package/ui/postcss.config.js +6 -0
  123. package/ui/src/App.tsx +753 -0
  124. package/ui/src/components/ui/button.tsx +48 -0
  125. package/ui/src/components/ui/card.tsx +50 -0
  126. package/ui/src/components/ui/input.tsx +21 -0
  127. package/ui/src/components/ui/label.tsx +20 -0
  128. package/ui/src/components/ui/select.tsx +80 -0
  129. package/ui/src/components/ui/switch.tsx +26 -0
  130. package/ui/src/components/ui/tabs.tsx +52 -0
  131. package/ui/src/index.css +33 -0
  132. package/ui/src/lib/api.ts +185 -0
  133. package/ui/src/lib/utils.ts +6 -0
  134. package/ui/src/main.tsx +10 -0
  135. package/ui/src/vite-env.d.ts +1 -0
  136. package/ui/tailwind.config.js +49 -0
  137. package/ui/tsconfig.json +25 -0
  138. package/ui/vite.config.ts +20 -0
@@ -0,0 +1,48 @@
1
+ import * as React from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cva, type VariantProps } from 'class-variance-authority';
4
+ import { cn } from '@/lib/utils';
5
+
6
+ const buttonVariants = cva(
7
+ 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
12
+ destructive: 'bg-red-500 text-white hover:bg-red-600',
13
+ outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
14
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
15
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
16
+ link: 'text-primary underline-offset-4 hover:underline',
17
+ },
18
+ size: {
19
+ default: 'h-9 px-4 py-2',
20
+ sm: 'h-8 rounded-md px-3 text-xs',
21
+ lg: 'h-10 rounded-md px-8',
22
+ icon: 'h-9 w-9',
23
+ },
24
+ },
25
+ defaultVariants: {
26
+ variant: 'default',
27
+ size: 'default',
28
+ },
29
+ }
30
+ );
31
+
32
+ export interface ButtonProps
33
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
34
+ VariantProps<typeof buttonVariants> {
35
+ asChild?: boolean;
36
+ }
37
+
38
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
39
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
40
+ const Comp = asChild ? Slot : 'button';
41
+ return (
42
+ <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
43
+ );
44
+ }
45
+ );
46
+ Button.displayName = 'Button';
47
+
48
+ export { Button, buttonVariants };
@@ -0,0 +1,50 @@
1
+ import * as React from 'react';
2
+ import { cn } from '@/lib/utils';
3
+
4
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
5
+ ({ className, ...props }, ref) => (
6
+ <div
7
+ ref={ref}
8
+ className={cn('rounded-xl border bg-card text-card-foreground shadow-sm', className)}
9
+ {...props}
10
+ />
11
+ )
12
+ );
13
+ Card.displayName = 'Card';
14
+
15
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
16
+ ({ className, ...props }, ref) => (
17
+ <div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
18
+ )
19
+ );
20
+ CardHeader.displayName = 'CardHeader';
21
+
22
+ const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
23
+ ({ className, ...props }, ref) => (
24
+ <h3 ref={ref} className={cn('font-semibold leading-none tracking-tight', className)} {...props} />
25
+ )
26
+ );
27
+ CardTitle.displayName = 'CardTitle';
28
+
29
+ const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
30
+ ({ className, ...props }, ref) => (
31
+ <p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
32
+ )
33
+ );
34
+ CardDescription.displayName = 'CardDescription';
35
+
36
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
37
+ ({ className, ...props }, ref) => (
38
+ <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
39
+ )
40
+ );
41
+ CardContent.displayName = 'CardContent';
42
+
43
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
44
+ ({ className, ...props }, ref) => (
45
+ <div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
46
+ )
47
+ );
48
+ CardFooter.displayName = 'CardFooter';
49
+
50
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+ import { cn } from '@/lib/utils';
3
+
4
+ const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
5
+ ({ className, type, ...props }, ref) => {
6
+ return (
7
+ <input
8
+ type={type}
9
+ className={cn(
10
+ 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
11
+ className
12
+ )}
13
+ ref={ref}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+ );
19
+ Input.displayName = 'Input';
20
+
21
+ export { Input };
@@ -0,0 +1,20 @@
1
+ import * as React from 'react';
2
+ import * as LabelPrimitive from '@radix-ui/react-label';
3
+ import { cn } from '@/lib/utils';
4
+
5
+ const Label = React.forwardRef<
6
+ React.ElementRef<typeof LabelPrimitive.Root>,
7
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
8
+ >(({ className, ...props }, ref) => (
9
+ <LabelPrimitive.Root
10
+ ref={ref}
11
+ className={cn(
12
+ 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ));
18
+ Label.displayName = LabelPrimitive.Root.displayName;
19
+
20
+ export { Label };
@@ -0,0 +1,80 @@
1
+ import * as React from 'react';
2
+ import * as SelectPrimitive from '@radix-ui/react-select';
3
+ import { cn } from '@/lib/utils';
4
+
5
+ const Select = SelectPrimitive.Root;
6
+ const SelectGroup = SelectPrimitive.Group;
7
+ const SelectValue = SelectPrimitive.Value;
8
+
9
+ const SelectTrigger = React.forwardRef<
10
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
11
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
12
+ >(({ className, children, ...props }, ref) => (
13
+ <SelectPrimitive.Trigger
14
+ ref={ref}
15
+ className={cn(
16
+ 'flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
17
+ className
18
+ )}
19
+ {...props}
20
+ >
21
+ {children}
22
+ <SelectPrimitive.Icon asChild>
23
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6"/></svg>
24
+ </SelectPrimitive.Icon>
25
+ </SelectPrimitive.Trigger>
26
+ ));
27
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
28
+
29
+ const SelectContent = React.forwardRef<
30
+ React.ElementRef<typeof SelectPrimitive.Content>,
31
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
32
+ >(({ className, children, position = 'popper', ...props }, ref) => (
33
+ <SelectPrimitive.Portal>
34
+ <SelectPrimitive.Content
35
+ ref={ref}
36
+ className={cn(
37
+ 'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md',
38
+ position === 'popper' && 'data-[side=bottom]:translate-y-1',
39
+ className
40
+ )}
41
+ position={position}
42
+ {...props}
43
+ >
44
+ <SelectPrimitive.Viewport
45
+ className={cn(
46
+ 'p-1',
47
+ position === 'popper' &&
48
+ 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
49
+ )}
50
+ >
51
+ {children}
52
+ </SelectPrimitive.Viewport>
53
+ </SelectPrimitive.Content>
54
+ </SelectPrimitive.Portal>
55
+ ));
56
+ SelectContent.displayName = SelectPrimitive.Content.displayName;
57
+
58
+ const SelectItem = React.forwardRef<
59
+ React.ElementRef<typeof SelectPrimitive.Item>,
60
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
61
+ >(({ className, children, ...props }, ref) => (
62
+ <SelectPrimitive.Item
63
+ ref={ref}
64
+ className={cn(
65
+ 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
66
+ className
67
+ )}
68
+ {...props}
69
+ >
70
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
71
+ <SelectPrimitive.ItemIndicator>
72
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
73
+ </SelectPrimitive.ItemIndicator>
74
+ </span>
75
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
76
+ </SelectPrimitive.Item>
77
+ ));
78
+ SelectItem.displayName = SelectPrimitive.Item.displayName;
79
+
80
+ export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectItem };
@@ -0,0 +1,26 @@
1
+ import * as React from 'react';
2
+ import * as SwitchPrimitives from '@radix-ui/react-switch';
3
+ import { cn } from '@/lib/utils';
4
+
5
+ const Switch = React.forwardRef<
6
+ React.ElementRef<typeof SwitchPrimitives.Root>,
7
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
8
+ >(({ className, ...props }, ref) => (
9
+ <SwitchPrimitives.Root
10
+ className={cn(
11
+ 'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
12
+ className
13
+ )}
14
+ {...props}
15
+ ref={ref}
16
+ >
17
+ <SwitchPrimitives.Thumb
18
+ className={cn(
19
+ 'pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0'
20
+ )}
21
+ />
22
+ </SwitchPrimitives.Root>
23
+ ));
24
+ Switch.displayName = SwitchPrimitives.Root.displayName;
25
+
26
+ export { Switch };
@@ -0,0 +1,52 @@
1
+ import * as React from 'react';
2
+ import * as TabsPrimitive from '@radix-ui/react-tabs';
3
+ import { cn } from '@/lib/utils';
4
+
5
+ const Tabs = TabsPrimitive.Root;
6
+
7
+ const TabsList = React.forwardRef<
8
+ React.ElementRef<typeof TabsPrimitive.List>,
9
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
10
+ >(({ className, ...props }, ref) => (
11
+ <TabsPrimitive.List
12
+ ref={ref}
13
+ className={cn(
14
+ 'inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground',
15
+ className
16
+ )}
17
+ {...props}
18
+ />
19
+ ));
20
+ TabsList.displayName = TabsPrimitive.List.displayName;
21
+
22
+ const TabsTrigger = React.forwardRef<
23
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
24
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
25
+ >(({ className, ...props }, ref) => (
26
+ <TabsPrimitive.Trigger
27
+ ref={ref}
28
+ className={cn(
29
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
30
+ className
31
+ )}
32
+ {...props}
33
+ />
34
+ ));
35
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
36
+
37
+ const TabsContent = React.forwardRef<
38
+ React.ElementRef<typeof TabsPrimitive.Content>,
39
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
40
+ >(({ className, ...props }, ref) => (
41
+ <TabsPrimitive.Content
42
+ ref={ref}
43
+ className={cn(
44
+ 'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
45
+ className
46
+ )}
47
+ {...props}
48
+ />
49
+ ));
50
+ TabsContent.displayName = TabsPrimitive.Content.displayName;
51
+
52
+ export { Tabs, TabsList, TabsTrigger, TabsContent };
@@ -0,0 +1,33 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 0 0% 100%;
8
+ --foreground: 222.2 84% 4.9%;
9
+ --card: 0 0% 100%;
10
+ --card-foreground: 222.2 84% 4.9%;
11
+ --primary: 221.2 83.2% 53.3%;
12
+ --primary-foreground: 210 40% 98%;
13
+ --secondary: 210 40% 96.1%;
14
+ --secondary-foreground: 222.2 47.4% 11.2%;
15
+ --muted: 210 40% 96.1%;
16
+ --muted-foreground: 215.4 16.3% 46.9%;
17
+ --accent: 210 40% 96.1%;
18
+ --accent-foreground: 222.2 47.4% 11.2%;
19
+ --border: 214.3 31.8% 91.4%;
20
+ --input: 214.3 31.8% 91.4%;
21
+ --ring: 221.2 83.2% 53.3%;
22
+ --radius: 0.5rem;
23
+ }
24
+ }
25
+
26
+ @layer base {
27
+ * {
28
+ @apply border-border;
29
+ }
30
+ body {
31
+ @apply bg-background text-foreground;
32
+ }
33
+ }
@@ -0,0 +1,185 @@
1
+ const API_BASE = '/api';
2
+
3
+ export interface Profile {
4
+ name: string;
5
+ baseUrl: string;
6
+ model: string;
7
+ opusModel?: string;
8
+ sonnetModel?: string;
9
+ haikuModel?: string;
10
+ protocol?: 'anthropic' | 'openai';
11
+ createdAt: string;
12
+ lastUsedAt?: string;
13
+ }
14
+
15
+ export interface McpServer {
16
+ name: string;
17
+ displayName: string;
18
+ description: string;
19
+ enabled: boolean;
20
+ }
21
+
22
+ export interface ExternalMcpServer {
23
+ name: string;
24
+ displayName: string;
25
+ description: string;
26
+ command: string;
27
+ args: string[];
28
+ envVars: Record<string, string>;
29
+ enabledByDefault: boolean;
30
+ }
31
+
32
+ export interface AllMcpServer {
33
+ name: string;
34
+ displayName: string;
35
+ description: string;
36
+ builtin: boolean;
37
+ enabledByDefault?: boolean;
38
+ enabled: boolean;
39
+ }
40
+
41
+ export async function getProfiles(): Promise<Profile[]> {
42
+ const res = await fetch(`${API_BASE}/profiles`);
43
+ if (!res.ok) throw new Error('Failed to fetch profiles');
44
+ return res.json();
45
+ }
46
+
47
+ export async function addProfile(profile: Omit<Profile, 'createdAt'> & { apiKey: string }): Promise<void> {
48
+ const res = await fetch(`${API_BASE}/profiles`, {
49
+ method: 'POST',
50
+ headers: { 'Content-Type': 'application/json' },
51
+ body: JSON.stringify(profile),
52
+ });
53
+ if (!res.ok) throw new Error('Failed to add profile');
54
+ }
55
+
56
+ export async function updateProfile(name: string, data: Partial<Profile> & { apiKey?: string }): Promise<void> {
57
+ const res = await fetch(`${API_BASE}/profiles/${encodeURIComponent(name)}`, {
58
+ method: 'PUT',
59
+ headers: { 'Content-Type': 'application/json' },
60
+ body: JSON.stringify(data),
61
+ });
62
+ if (!res.ok) throw new Error('Failed to update profile');
63
+ }
64
+
65
+ export async function deleteProfile(name: string): Promise<void> {
66
+ const res = await fetch(`${API_BASE}/profiles/${name}`, { method: 'DELETE' });
67
+ if (!res.ok) throw new Error('Failed to delete profile');
68
+ }
69
+
70
+ export async function setDefaultProfile(name: string): Promise<void> {
71
+ const res = await fetch(`${API_BASE}/profiles/${name}/default`, { method: 'PUT' });
72
+ if (!res.ok) throw new Error('Failed to set default profile');
73
+ }
74
+
75
+ export async function getMcpServers(): Promise<McpServer[]> {
76
+ const res = await fetch(`${API_BASE}/mcp`);
77
+ if (!res.ok) throw new Error('Failed to fetch MCP servers');
78
+ return res.json();
79
+ }
80
+
81
+ export async function toggleMcp(name: string, enabled: boolean, instance?: string): Promise<void> {
82
+ const url = instance
83
+ ? `${API_BASE}/mcp/${name}/${enabled ? 'enable' : 'disable'}?instance=${encodeURIComponent(instance)}`
84
+ : `${API_BASE}/mcp/${name}/${enabled ? 'enable' : 'disable'}`;
85
+ const res = await fetch(url, { method: 'PUT' });
86
+ if (!res.ok) throw new Error('Failed to toggle MCP server');
87
+ }
88
+
89
+ export async function getAllMcpServers(instance?: string): Promise<AllMcpServer[]> {
90
+ const url = instance
91
+ ? `${API_BASE}/mcp/all?instance=${encodeURIComponent(instance)}`
92
+ : `${API_BASE}/mcp/all`;
93
+ const res = await fetch(url);
94
+ if (!res.ok) throw new Error('Failed to fetch all MCP servers');
95
+ return res.json();
96
+ }
97
+
98
+ export async function getExternalMcpServers(): Promise<ExternalMcpServer[]> {
99
+ const res = await fetch(`${API_BASE}/mcp/external`);
100
+ if (!res.ok) throw new Error('Failed to fetch external MCP servers');
101
+ return res.json();
102
+ }
103
+
104
+ export async function addExternalMcpServer(server: ExternalMcpServer): Promise<void> {
105
+ const res = await fetch(`${API_BASE}/mcp/external`, {
106
+ method: 'POST',
107
+ headers: { 'Content-Type': 'application/json' },
108
+ body: JSON.stringify(server),
109
+ });
110
+ if (!res.ok) throw new Error('Failed to add external MCP server');
111
+ }
112
+
113
+ export async function removeExternalMcpServer(name: string): Promise<void> {
114
+ const res = await fetch(`${API_BASE}/mcp/external/${encodeURIComponent(name)}`, {
115
+ method: 'DELETE',
116
+ });
117
+ if (!res.ok) throw new Error('Failed to remove external MCP server');
118
+ }
119
+
120
+ export async function ping(): Promise<boolean> {
121
+ try {
122
+ const res = await fetch(`${API_BASE}/ping`);
123
+ return res.ok;
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+
129
+ export async function getStatus(): Promise<{ currentProfile?: string }> {
130
+ const res = await fetch(`${API_BASE}/status`);
131
+ if (!res.ok) throw new Error('Failed to fetch status');
132
+ return res.json();
133
+ }
134
+
135
+ // MCP Config types and API
136
+
137
+ export interface WebSearchProviderConfig {
138
+ enabled: boolean;
139
+ apiKey?: string;
140
+ }
141
+
142
+ export interface ImageAnalysisProviderConfig {
143
+ enabled: boolean;
144
+ baseUrl: string;
145
+ apiKey: string;
146
+ model: string;
147
+ format: 'anthropic' | 'openai';
148
+ }
149
+
150
+ export interface McpConfig {
151
+ websearch: {
152
+ enabled: boolean;
153
+ providers: Record<string, WebSearchProviderConfig>;
154
+ };
155
+ imageAnalysis: {
156
+ enabled: boolean;
157
+ providers: Record<string, ImageAnalysisProviderConfig>;
158
+ };
159
+ }
160
+
161
+ export interface ProviderPresets {
162
+ websearch: Record<string, { name: string; needsApiKey: boolean; description: string }>;
163
+ imageAnalysis: Record<string, { name: string; format: string; baseUrl: string; models: string[] }>;
164
+ }
165
+
166
+ export async function getMcpConfig(): Promise<McpConfig> {
167
+ const res = await fetch(`${API_BASE}/mcp-config`);
168
+ if (!res.ok) throw new Error('Failed to fetch MCP config');
169
+ return res.json();
170
+ }
171
+
172
+ export async function updateMcpConfig(config: McpConfig): Promise<void> {
173
+ const res = await fetch(`${API_BASE}/mcp-config`, {
174
+ method: 'PUT',
175
+ headers: { 'Content-Type': 'application/json' },
176
+ body: JSON.stringify(config),
177
+ });
178
+ if (!res.ok) throw new Error('Failed to update MCP config');
179
+ }
180
+
181
+ export async function getProviderPresets(): Promise<ProviderPresets> {
182
+ const res = await fetch(`${API_BASE}/mcp-config/presets`);
183
+ if (!res.ok) throw new Error('Failed to fetch provider presets');
184
+ return res.json();
185
+ }
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App';
4
+ import './index.css';
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ );
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,49 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ darkMode: ['class'],
4
+ content: ['./index.html', './src/**/*.{ts,tsx}'],
5
+ theme: {
6
+ container: {
7
+ center: true,
8
+ padding: '2rem',
9
+ screens: {
10
+ '2xl': '1400px',
11
+ },
12
+ },
13
+ extend: {
14
+ colors: {
15
+ border: 'hsl(var(--border))',
16
+ input: 'hsl(var(--input))',
17
+ ring: 'hsl(var(--ring))',
18
+ background: 'hsl(var(--background))',
19
+ foreground: 'hsl(var(--foreground))',
20
+ primary: {
21
+ DEFAULT: 'hsl(var(--primary))',
22
+ foreground: 'hsl(var(--primary-foreground))',
23
+ },
24
+ secondary: {
25
+ DEFAULT: 'hsl(var(--secondary))',
26
+ foreground: 'hsl(var(--secondary-foreground))',
27
+ },
28
+ muted: {
29
+ DEFAULT: 'hsl(var(--muted))',
30
+ foreground: 'hsl(var(--muted-foreground))',
31
+ },
32
+ accent: {
33
+ DEFAULT: 'hsl(var(--accent))',
34
+ foreground: 'hsl(var(--accent-foreground))',
35
+ },
36
+ card: {
37
+ DEFAULT: 'hsl(var(--card))',
38
+ foreground: 'hsl(var(--card-foreground))',
39
+ },
40
+ },
41
+ borderRadius: {
42
+ lg: 'var(--radius)',
43
+ md: 'calc(var(--radius) - 2px)',
44
+ sm: 'calc(var(--radius) - 4px)',
45
+ },
46
+ },
47
+ },
48
+ plugins: [],
49
+ };
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "noUncheckedSideEffectImports": true,
19
+ "baseUrl": ".",
20
+ "paths": {
21
+ "@/*": ["./src/*"]
22
+ }
23
+ },
24
+ "include": ["src"]
25
+ }
@@ -0,0 +1,20 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import path from 'path';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ '@': path.resolve(__dirname, './src'),
10
+ },
11
+ },
12
+ server: {
13
+ proxy: {
14
+ '/api': {
15
+ target: 'http://localhost:3000',
16
+ changeOrigin: true,
17
+ },
18
+ },
19
+ },
20
+ });