@hileeon/mcc 0.1.3 → 0.1.5
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/dist/dashboard-server.js +1 -1
- package/dist/dashboard-server.js.map +1 -1
- package/dist/mcc.js +1 -1
- package/dist/ui/assets/index-B16lhKZ6.js +40 -0
- package/dist/ui/assets/index-jEfiB6-h.css +1 -0
- package/{ui → dist/ui}/index.html +3 -2
- package/package.json +8 -2
- package/.claude/CLAUDE.md +0 -204
- package/.claude/agents/.gitkeep +0 -0
- package/.claude/settings.json +0 -9
- package/.claude/skills/.gitkeep +0 -0
- package/docs/decisions.md +0 -33
- package/docs/lessons.md +0 -8
- package/docs/product.md +0 -37
- package/src/accounts/instance-manager.ts +0 -58
- package/src/accounts/shared-manager.ts +0 -154
- package/src/accounts/store.ts +0 -111
- package/src/core/model-router.ts +0 -82
- package/src/dashboard-server.ts +0 -427
- package/src/mcc.ts +0 -482
- package/src/mcp/external-registry.ts +0 -73
- package/src/mcp/installer.ts +0 -258
- package/src/mcp/mcp-config.ts +0 -168
- package/src/mcp/registry.ts +0 -89
- package/src/proxy/proxy-daemon.ts +0 -184
- package/src/proxy/proxy-entry.ts +0 -63
- package/src/proxy/proxy-paths.ts +0 -97
- package/src/proxy/proxy-server.ts +0 -278
- package/src/proxy/upstream-url.ts +0 -38
- package/src/shared/logger.ts +0 -140
- package/src/shared/provider-preset-catalog.ts +0 -340
- package/tsconfig.json +0 -33
- package/ui/.prettierrc +0 -9
- package/ui/package.json +0 -33
- package/ui/postcss.config.js +0 -6
- package/ui/src/App.tsx +0 -753
- package/ui/src/components/ui/button.tsx +0 -48
- package/ui/src/components/ui/card.tsx +0 -50
- package/ui/src/components/ui/input.tsx +0 -21
- package/ui/src/components/ui/label.tsx +0 -20
- package/ui/src/components/ui/select.tsx +0 -80
- package/ui/src/components/ui/switch.tsx +0 -26
- package/ui/src/components/ui/tabs.tsx +0 -52
- package/ui/src/index.css +0 -33
- package/ui/src/lib/api.ts +0 -185
- package/ui/src/lib/utils.ts +0 -6
- package/ui/src/main.tsx +0 -10
- package/ui/src/vite-env.d.ts +0 -1
- package/ui/tailwind.config.js +0 -49
- package/ui/tsconfig.json +0 -25
- package/ui/vite.config.ts +0 -20
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--background: 0 0% 100%;--foreground: 222.2 84% 4.9%;--card: 0 0% 100%;--card-foreground: 222.2 84% 4.9%;--primary: 221.2 83.2% 53.3%;--primary-foreground: 210 40% 98%;--secondary: 210 40% 96.1%;--secondary-foreground: 222.2 47.4% 11.2%;--muted: 210 40% 96.1%;--muted-foreground: 215.4 16.3% 46.9%;--accent: 210 40% 96.1%;--accent-foreground: 222.2 47.4% 11.2%;--border: 214.3 31.8% 91.4%;--input: 214.3 31.8% 91.4%;--ring: 221.2 83.2% 53.3%;--radius: .5rem}*{border-color:hsl(var(--border))}body{background-color:hsl(var(--background));color:hsl(var(--foreground))}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.right-2{right:.5rem}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.h-10{height:2.5rem}.h-2{height:.5rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[var\(--radix-select-trigger-height\)\]{height:var(--radix-select-trigger-height)}.h-screen{height:100vh}.max-h-96{max-height:24rem}.min-h-screen{min-height:100vh}.w-14{width:3.5rem}.w-2{width:.5rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-9{width:2.25rem}.w-full{width:100%}.min-w-\[8rem\]{min-width:8rem}.min-w-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.max-w-4xl{max-width:56rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-t{border-top-width:1px}.border-amber-200{--tw-border-opacity: 1;border-color:rgb(253 230 138 / var(--tw-border-opacity, 1))}.border-border\/50{border-color:hsl(var(--border) / .5)}.border-green-200{--tw-border-opacity: 1;border-color:rgb(187 247 208 / var(--tw-border-opacity, 1))}.border-input{border-color:hsl(var(--input))}.border-transparent{border-color:transparent}.bg-amber-50{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity, 1))}.bg-background{background-color:hsl(var(--background))}.bg-card{background-color:hsl(var(--card))}.bg-emerald-50{--tw-bg-opacity: 1;background-color:rgb(236 253 245 / var(--tw-bg-opacity, 1))}.bg-green-50{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-muted{background-color:hsl(var(--muted))}.bg-primary{background-color:hsl(var(--primary))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-secondary{background-color:hsl(var(--secondary))}.bg-transparent{background-color:transparent}.p-1{padding:.25rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pl-2{padding-left:.5rem}.pr-8{padding-right:2rem}.pt-0{padding-top:0}.pt-2{padding-top:.5rem}.pt-5{padding-top:1.25rem}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-none{line-height:1}.tracking-tight{letter-spacing:-.025em}.text-amber-700{--tw-text-opacity: 1;color:rgb(180 83 9 / var(--tw-text-opacity, 1))}.text-card-foreground{color:hsl(var(--card-foreground))}.text-emerald-700{--tw-text-opacity: 1;color:rgb(4 120 87 / var(--tw-text-opacity, 1))}.text-foreground\/80{color:hsl(var(--foreground) / .8)}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-green-700{--tw-text-opacity: 1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-muted-foreground\/60{color:hsl(var(--muted-foreground) / .6)}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline-offset-4{text-underline-offset:4px}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-0{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-inset{--tw-ring-inset: inset}.ring-emerald-600\/20{--tw-ring-color: rgb(5 150 105 / .2)}.ring-offset-background{--tw-ring-offset-color: hsl(var(--background))}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.file\:border-0::file-selector-button{border-width:0px}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.placeholder\:text-muted-foreground::-moz-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary) / .9)}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary) / .8)}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:underline:hover{text-decoration-line:underline}.focus\:bg-accent:focus{background-color:hsl(var(--accent))}.focus\:text-accent-foreground:focus{color:hsl(var(--accent-foreground))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-ring:focus{--tw-ring-color: hsl(var(--ring))}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color: hsl(var(--ring))}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width: 2px}.focus-visible\:ring-offset-background:focus-visible{--tw-ring-offset-color: hsl(var(--background))}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-70{opacity:.7}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[state\=checked\]\:translate-x-4[data-state=checked]{--tw-translate-x: 1rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[state\=unchecked\]\:translate-x-0[data-state=unchecked]{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[state\=active\]\:bg-background[data-state=active]{background-color:hsl(var(--background))}.data-\[state\=checked\]\:bg-primary[data-state=checked]{background-color:hsl(var(--primary))}.data-\[state\=unchecked\]\:bg-input[data-state=unchecked]{background-color:hsl(var(--input))}.data-\[state\=active\]\:text-foreground[data-state=active]{color:hsl(var(--foreground))}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[state\=active\]\:shadow[data-state=active]{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.dark\:border-amber-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(146 64 14 / var(--tw-border-opacity, 1))}.dark\:border-green-800:is(.dark *){--tw-border-opacity: 1;border-color:rgb(22 101 52 / var(--tw-border-opacity, 1))}.dark\:bg-amber-950:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(69 26 3 / var(--tw-bg-opacity, 1))}.dark\:bg-emerald-950:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(2 44 34 / var(--tw-bg-opacity, 1))}.dark\:bg-green-950:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(5 46 22 / var(--tw-bg-opacity, 1))}.dark\:text-amber-400:is(.dark *){--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.dark\:text-emerald-400:is(.dark *){--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.dark\:text-green-400:is(.dark *){--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.dark\:ring-emerald-500\/20:is(.dark *){--tw-ring-color: rgb(16 185 129 / .2)}@media(min-width:768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>MCC Dashboard</title>
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-B16lhKZ6.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-jEfiB6-h.css">
|
|
7
9
|
</head>
|
|
8
10
|
<body>
|
|
9
|
-
<div id="root"></div>
|
|
10
|
-
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
<div id="root"></div>
|
|
11
12
|
</body>
|
|
12
13
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hileeon/mcc",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "My Cloud Code - Multi-provider account and model switching for Claude Code",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Zhang Chicheng",
|
|
@@ -13,13 +13,19 @@
|
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.0.0"
|
|
15
15
|
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"lib"
|
|
19
|
+
],
|
|
16
20
|
"scripts": {
|
|
17
21
|
"build": "tsc && node -e \"const fs=require('fs');const p='dist/mcc.js';const c=fs.readFileSync(p,'utf8');fs.writeFileSync(p,'#!/usr/bin/env node\\n'+c);\"",
|
|
18
22
|
"build:watch": "tsc --watch",
|
|
19
23
|
"typecheck": "tsc --noEmit",
|
|
20
24
|
"dev": "node dist/mcc.js",
|
|
21
25
|
"build:ui": "cd ui && npm install && npm run build",
|
|
22
|
-
"
|
|
26
|
+
"build:all": "npm run build && npm run build:ui",
|
|
27
|
+
"dashboard": "npm run build && npm run build:ui && node dist/dashboard-server.js",
|
|
28
|
+
"prepublishOnly": "npm run build:all"
|
|
23
29
|
},
|
|
24
30
|
"dependencies": {
|
|
25
31
|
"cors": "^2.8.5",
|
package/.claude/CLAUDE.md
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
# MCC - My Cloud Code
|
|
2
|
-
|
|
3
|
-
## 项目概述
|
|
4
|
-
|
|
5
|
-
MCC 是一个轻量级多账号切换工具,用于在多个 Claude Code 账号(不同 API Provider)之间快速切换。
|
|
6
|
-
|
|
7
|
-
核心功能:
|
|
8
|
-
- 多 profile 管理(deepseek、qwen、glm、km、mm、anthropic)
|
|
9
|
-
- 直接 API 模式和 OpenAI 兼容模式(自动翻译 proxy)
|
|
10
|
-
- MCP 工具支持(WebSearch、ImageAnalysis)
|
|
11
|
-
- 每个 profile 独立的 `CLAUDE_CONFIG_DIR` 隔离
|
|
12
|
-
- 跨 profile 共享 skills/commands/agents/plugins/settings
|
|
13
|
-
|
|
14
|
-
## 设计原则
|
|
15
|
-
|
|
16
|
-
- **KISS**:简单直接,不用 OAuth
|
|
17
|
-
- **File-based**:API Key 存 `~/.mcc/profiles/<name>/.key`
|
|
18
|
-
- **YAGNI**:只做需要的功能
|
|
19
|
-
|
|
20
|
-
## 目录结构
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
src/
|
|
24
|
-
├── mcc.ts # CLI 入口
|
|
25
|
-
├── accounts/
|
|
26
|
-
│ ├── store.ts # Profile 元数据存储
|
|
27
|
-
│ ├── instance-manager.ts # CLAUDE_CONFIG_DIR 隔离
|
|
28
|
-
│ └── shared-manager.ts # 跨 instance 共享目录(symlink)
|
|
29
|
-
├── core/
|
|
30
|
-
│ └── model-router.ts # Profile env 构建 + tiered model
|
|
31
|
-
├── mcp/
|
|
32
|
-
│ ├── registry.ts # MCP 服务器注册表(内置)
|
|
33
|
-
│ ├── installer.ts # MCP 安装到 instance
|
|
34
|
-
│ ├── external-registry.ts # 外部 MCP 注册表
|
|
35
|
-
│ └── mcp-config.ts # WebSearch/ImageAnalysis provider 配置
|
|
36
|
-
├── proxy/
|
|
37
|
-
│ ├── proxy-server.ts # OpenAI→Anthropic 翻译服务器
|
|
38
|
-
│ ├── proxy-daemon.ts # Proxy 生命周期管理
|
|
39
|
-
│ ├── proxy-entry.ts # Proxy 进程入口
|
|
40
|
-
│ ├── proxy-paths.ts # PID/session 文件路径
|
|
41
|
-
│ └── upstream-url.ts # upstream URL 解析
|
|
42
|
-
├── dashboard-server.ts # Express Dashboard API
|
|
43
|
-
└── shared/
|
|
44
|
-
├── logger.ts # 日志系统(session-based,logrotate)
|
|
45
|
-
└── provider-preset-catalog.ts
|
|
46
|
-
|
|
47
|
-
lib/
|
|
48
|
-
├── mcp/ # MCP server JS 文件
|
|
49
|
-
│ ├── mcc-websearch-server.cjs
|
|
50
|
-
│ └── mcc-image-analysis-server.cjs
|
|
51
|
-
├── mcp-hooks/ # MCP 运行时 hook
|
|
52
|
-
│ ├── logger.cjs
|
|
53
|
-
│ ├── image-analysis-runtime.cjs
|
|
54
|
-
│ ├── image-analyzer-transformer.cjs
|
|
55
|
-
│ └── websearch-transformer.cjs
|
|
56
|
-
└── shared/
|
|
57
|
-
└── logger.cjs
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## 存储结构
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
~/.mcc/
|
|
64
|
-
├── profiles.json # Profile 元数据
|
|
65
|
-
├── profiles/ # per-profile
|
|
66
|
-
│ └── <name>/.key # API key
|
|
67
|
-
├── instances/ # per-profile isolated dirs
|
|
68
|
-
│ └── <name>/
|
|
69
|
-
│ ├── .claude.json # Claude Code 实际读取的 MCP 配置
|
|
70
|
-
│ ├── .mcp/mcpServers.json # 参考副本
|
|
71
|
-
│ └── (CLAUDE_CONFIG_DIR subdirs)
|
|
72
|
-
├── mcp/ # 内置 MCP server 文件
|
|
73
|
-
├── mcp-hooks/ # MCP runtime hook 文件
|
|
74
|
-
├── external-mcp-servers.json # 用户添加的外部 MCP 定义
|
|
75
|
-
├── mcp-config.json # WebSearch/ImageAnalysis provider 配置
|
|
76
|
-
└── proxy/ # Proxy PID/session 文件
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## 开发
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
npm install
|
|
83
|
-
npm run build # tsc
|
|
84
|
-
npm run dev -- help # 直接跑 dist/
|
|
85
|
-
npm run build:ui # 构建 React Dashboard
|
|
86
|
-
npm run dashboard # 编译 + 启动 Dashboard(端口 3000)
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## CLI 命令
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
mcc <profile> [args...] # 用指定 profile 启动 Claude Code
|
|
93
|
-
mcc profile add <name> # 添加 profile
|
|
94
|
-
mcc profile list # 列出所有 profile
|
|
95
|
-
mcc profile remove <name> # 删除 profile
|
|
96
|
-
mcc profile default [name] # 查询或设默认 profile
|
|
97
|
-
mcc mcp list # 列出所有 MCP server
|
|
98
|
-
mcc mcp add --name <id> --command <cmd> [--display-name...] [--args...] [--provider-ref...] # 添加外部 MCP
|
|
99
|
-
mcc mcp remove <name> # 删除外部 MCP
|
|
100
|
-
mcc mcp enable <name> <profile> # 在指定 profile 启用外部 MCP
|
|
101
|
-
mcc mcp disable <name> <profile> # 在指定 profile 禁用外部 MCP
|
|
102
|
-
mcc dashboard # 启动 Web Dashboard(端口 3000)
|
|
103
|
-
mcc help # 显示帮助
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Profile 与 Protocol
|
|
107
|
-
|
|
108
|
-
每个 profile 支持 `protocol: 'anthropic'`(默认,直接 API)或 `protocol: 'openai'`(通过翻译 proxy)。
|
|
109
|
-
|
|
110
|
-
OpenAI 兼容 profile 启动时自动在 `127.0.0.1:43456-43555` 范围内启动一个 OpenAI→Anthropic 翻译 proxy,Claude Code 的请求经过本地 proxy 转发给 upstream OpenAI-compatible API。
|
|
111
|
-
|
|
112
|
-
## MCP 工具
|
|
113
|
-
|
|
114
|
-
内置两个 MCP server:
|
|
115
|
-
- `mcc-websearch` - Web 搜索(Exa/Tavily/Brave/DuckDuckGo)
|
|
116
|
-
- `mcc-image-analysis` - 图片分析(支持 OpenAI vision 格式)
|
|
117
|
-
|
|
118
|
-
外部 MCP 通过 `mcc mcp add` 注册,支持 `${MCC_PROVIDER_KEY:<providerId>}` 引用 `mcp-config.json` 里配置的 API key。
|
|
119
|
-
|
|
120
|
-
MCP provider 配置在 `~/.mcc/mcp-config.json`。
|
|
121
|
-
|
|
122
|
-
## 记忆索引
|
|
123
|
-
|
|
124
|
-
- `docs/product.md` — 理解产品意图和范围时读
|
|
125
|
-
- `docs/decisions.md` — 做架构 / 选型决策前先看有无先例
|
|
126
|
-
- `docs/lessons.md` — 遇到诡异现象时优先 grep,可能已有人踩过
|
|
127
|
-
|
|
128
|
-
<!-- BEGIN: PROJECT MEMORY PROTOCOL (injected by project-memory-init skill) -->
|
|
129
|
-
|
|
130
|
-
## 项目记忆维护协议
|
|
131
|
-
|
|
132
|
-
本项目维护三份长期记忆文件:
|
|
133
|
-
|
|
134
|
-
- `docs/product.md` — 当前产品是什么、为谁做、不做什么(滚动更新)
|
|
135
|
-
- `docs/decisions.md` — 重大技术决策的追加式记录(只增不改)
|
|
136
|
-
- `docs/lessons.md` — 踩过的坑与教训(追加为主,修复后剪枝)
|
|
137
|
-
|
|
138
|
-
### 时间戳规范
|
|
139
|
-
|
|
140
|
-
追加到 `decisions.md` / `lessons.md` 以及任何归档注释块的条目标题,**必须**使用 `YYYY-MM-DD HH:MM:SS +TZ` 格式(24 小时制 + 时区),例:`2026-04-25 14:30:45 +08:00`。不要只写日期。
|
|
141
|
-
|
|
142
|
-
**为什么**:本项目可能同时开多个 git worktree 改同一份记忆文件,仅凭日期无法稳定排序——合并时容易冲突,错误记录也无法对回到当时的代码状态。
|
|
143
|
-
|
|
144
|
-
**读取时以时间戳为准,不要以文件中出现的顺序为准**:worktree 合并 / 并行追加 / Claude 误插位置等场景下,新条目可能落在文件中较旧条目之后但实际时间更早。判断"哪条最新 / 谁 supersede 谁 / 同一天的先后"一律看 header 里的时间戳,**不要**靠文件位置推断。位置错乱按 `decisions.md` 的「只增不改」原则不主动重排——动既往位置反而违反追加式协议。
|
|
145
|
-
|
|
146
|
-
**获取当前时刻**:
|
|
147
|
-
|
|
148
|
-
- bash:`date "+%Y-%m-%d %H:%M:%S %:z"`
|
|
149
|
-
- PowerShell:`Get-Date -Format "yyyy-MM-dd HH:mm:ss zzz"`
|
|
150
|
-
|
|
151
|
-
`product.md` 是滚动更新文件,不受此规范约束。
|
|
152
|
-
|
|
153
|
-
### 在以下情况主动提议沉淀
|
|
154
|
-
|
|
155
|
-
1. **做出非平凡的技术决策**(选型、架构变更、推翻旧方案、引入新依赖)→ 提议追加到 `decisions.md`
|
|
156
|
-
2. **修复一个花了 30 分钟以上才定位的 bug** → 提议追加到 `lessons.md`
|
|
157
|
-
3. **用户说"砍掉 X 特性" / "改成 Y 方案" / "这个方向不对"** → 提议更新 `product.md`
|
|
158
|
-
4. **发现代码里有"看起来奇怪但其实有原因"的地方** → 提议追加到 `lessons.md`
|
|
159
|
-
|
|
160
|
-
**提议方式**:给出**具体的文字草稿**,不要只问"要不要记"。让用户直接回复"记"或"不用"。
|
|
161
|
-
|
|
162
|
-
**决定权在用户**:说"记"则写入,说"不用"则跳过。**绝不自作主张更新文件**。
|
|
163
|
-
|
|
164
|
-
### `.claude/CLAUDE.md` 与 `README.md` 同步检查
|
|
165
|
-
|
|
166
|
-
`decisions.md` / `lessons.md` 沉淀**历史**;`.claude/CLAUDE.md` 和 `README.md` 描述**当前状态**——前者给未来的 Claude 读,后者给人类读。两份"门面文档"最容易悄悄落后于代码。
|
|
167
|
-
|
|
168
|
-
**触发时机**:上面四类触发里的 #1(非平凡技术决策)和 #3(方向性变化)发生时——简单说,**任何值得写进 `decisions.md` 或改动 `product.md` 的变化**,都要顺带跑一次本节的检查。
|
|
169
|
-
|
|
170
|
-
**检查做什么**:
|
|
171
|
-
|
|
172
|
-
1. 把这次的变化摘出来
|
|
173
|
-
2. 对照读 `.claude/CLAUDE.md` 和 `README.md`(两份都看,不是二选一)
|
|
174
|
-
3. 找三类不一致:
|
|
175
|
-
- **事实相矛盾**(例:"技术栈 = X" 但实际已切成 Y)
|
|
176
|
-
- **功能说明指向已删功能**(例:README 还在教装某个已撤掉的模块)
|
|
177
|
-
- **漏记新能力**(例:新增了 `install.sh`、新目录、新脚本,文件结构树没补)
|
|
178
|
-
4. 有不一致时,**分别**给出 `.claude/CLAUDE.md` 和 `README.md` 的修改草稿让用户确认——两份受众和粒度不同,不要套用同一份草稿
|
|
179
|
-
5. 两份都没问题也要**明确说一句**"CLAUDE.md / README.md 已核对,无需改动",避免"默默跳过"让用户不确定你查没查
|
|
180
|
-
|
|
181
|
-
**边界**:
|
|
182
|
-
|
|
183
|
-
- `decisions.md` 记"为什么做这个决策";`.claude/CLAUDE.md` / `README.md` 记"当前是什么状态"。同一变化在多处出现是正常的,角度不同
|
|
184
|
-
- 不要把决策的背景 / 选项 / 代价抄到 CLAUDE.md 或 README.md 里,**只抄结论**(当前事实)
|
|
185
|
-
- 没有实际变化就不改——不要为了"看起来在维护"做无意义修订
|
|
186
|
-
|
|
187
|
-
### 以下情况不要记录
|
|
188
|
-
|
|
189
|
-
- 小 bug、typo、无需思考就能修的问题
|
|
190
|
-
- 事后看来显然的决策("obvious in retrospect")
|
|
191
|
-
- 可以直接从代码看出的事实
|
|
192
|
-
- 临时讨论、没落地的想法
|
|
193
|
-
|
|
194
|
-
### 会话状态不落盘
|
|
195
|
-
|
|
196
|
-
"当前在做什么 / 卡在哪 / 下一步做什么"是会话状态,不是项目记忆——留在对话里,不写进任何文件。
|
|
197
|
-
|
|
198
|
-
### 各文件的变更模式
|
|
199
|
-
|
|
200
|
-
- `product.md`:滚动更新。方向有较大调整时重写对应章节。
|
|
201
|
-
- `decisions.md`:**追加式,只增不改**。历史决策永远不修改;如被推翻,新建条目并在其中引用旧条目(如 "Supersedes 2026-01-15 14:30:45 +08:00 的条目")。
|
|
202
|
-
- `lessons.md`:追加为主。坑被彻底修复后可剪枝对应条目;有长期警示价值的保留。
|
|
203
|
-
|
|
204
|
-
<!-- END: PROJECT MEMORY PROTOCOL -->
|
package/.claude/agents/.gitkeep
DELETED
|
File without changes
|
package/.claude/settings.json
DELETED
package/.claude/skills/.gitkeep
DELETED
|
File without changes
|
package/docs/decisions.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# 技术决策记录
|
|
2
|
-
|
|
3
|
-
本文件追加记录本项目的重大技术决策。新决策追加到文件末尾;历史条目不修改;如某决策被推翻,新建条目并引用被推翻的条目(例如 "Supersedes 2026-01-15 14:30:45 +08:00 的条目")。所有条目标题必须带 24 小时制时分秒与时区,详见「时间戳规范」章节。
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 2026-05-14 17:37:50 +08:00: TypeScript + CommonJS + tsc,不引入 bundler
|
|
8
|
-
|
|
9
|
-
**背景**: 项目是 Node.js CLI 工具,需要编译到 JS 后通过 `node dist/mcc.js` 运行。
|
|
10
|
-
|
|
11
|
-
**选项**:
|
|
12
|
-
- esbuild / tsx 等快速工具:开发体验好但多一个依赖
|
|
13
|
-
- tsc:零额外依赖,TypeScript 自带
|
|
14
|
-
|
|
15
|
-
**选择**: `tsc` + CommonJS(`module: "commonjs"`)+ ES2020 target。`npm run build` 即 `tsc`,`npm run dev` 直接 `node dist/mcc.js`。
|
|
16
|
-
|
|
17
|
-
**代价**: 放弃了 esbuild 的 sub-second 编译速度和 ESM 生态。后续如果编译时间明显影响开发体验可重新评估。
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## 2026-05-14 17:37:50 +08:00: KISS direct-API 路线——不做 OAuth、不做本地 proxy
|
|
22
|
-
|
|
23
|
-
**背景**: 现有工具 CCS 已经覆盖了 OAuth + local proxy + 多 runtime 的完整场景。MCC 的定位是极简替代——给只需要 API key 切换的用户一个更轻的选择。
|
|
24
|
-
|
|
25
|
-
**选项**:
|
|
26
|
-
- 沿用 CCS 架构(OAuth + proxy):功能完整但复杂度高
|
|
27
|
-
- 纯 CLI wrapper:直接设 env 然后 spawn Claude Code
|
|
28
|
-
|
|
29
|
-
**选择**: 纯 env-based direct API。`buildDirectApiEnv()` 返回 `ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN` / `ANTHROPIC_MODEL`,`cmdUse()` 拼上 `CLAUDE_CONFIG_DIR` 后 `spawn('claude', ...)`。
|
|
30
|
-
|
|
31
|
-
**代价**: 不支持 OAuth provider(Codex、Kiro 等),不支持 `profile:model` 运行时路由。这些场景留给 CCS。
|
|
32
|
-
|
|
33
|
-
---
|
package/docs/lessons.md
DELETED
package/docs/product.md
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# Product
|
|
2
|
-
|
|
3
|
-
## 是什么
|
|
4
|
-
|
|
5
|
-
MCC(My Cloud Code)是一个轻量级 CLI 工具,用于在多个 Claude Code 账号(不同 API Provider)之间快速切换。不走 OAuth——直接设置环境变量后 `spawn` Claude Code。
|
|
6
|
-
|
|
7
|
-
支持两种协议模式:
|
|
8
|
-
- **Anthropic 直接模式**:profile 的 `baseUrl` 直接对接 Anthropic 兼容 API
|
|
9
|
-
- **OpenAI 兼容模式**(`protocol: 'openai'`):自动启动本地翻译 proxy,把 Claude Code 的 Anthropic 请求转换为 OpenAI chat/completions 格式,转发给 upstream provider
|
|
10
|
-
|
|
11
|
-
## 为谁做 / 解决什么问题
|
|
12
|
-
|
|
13
|
-
为需要频繁在多个 AI provider(deepseek、qwen、glm、kimi、minimax、anthropic 等)之间切换的开发者,省去每次手动改 config、改环境变量的机械劳动。
|
|
14
|
-
|
|
15
|
-
## 当前核心功能
|
|
16
|
-
|
|
17
|
-
- **多 profile 管理**:`mcc profile add/list/remove/default`,元数据存 `~/.mcc/profiles.json`,API key 存 `~/.mcc/profiles/<name>/.key`
|
|
18
|
-
- **一键切换**:`mcc <profile>` 设置 `ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN` / `ANTHROPIC_MODEL` / `CLAUDE_CONFIG_DIR` 后直接启动 Claude Code
|
|
19
|
-
- **实例隔离**:每个 profile 对应独立的 `CLAUDE_CONFIG_DIR`(`~/.mcc/instances/<name>/`),session 历史、MCP 配置互不干扰
|
|
20
|
-
- **OpenAI 兼容 + 翻译 proxy**:`protocol: 'openai'` 时自动在 `127.0.0.1:43456-43555` 范围内启动 proxy,支持所有 OpenAI-compatible provider(MiniMax 等)
|
|
21
|
-
- **Tiered model**:profile 支持 `opusModel`/`sonnetModel`/`haikuModel`,Claude Code 运行时根据任务级别自动选用
|
|
22
|
-
- **内置 MCP**:`mcc-websearch`(多源 web 搜索)和 `mcc-image-analysis`(图片/PDF 分析),自动安装到 instance 的 `.claude.json`
|
|
23
|
-
- **外部 MCP registry**:通过 `mcc mcp add` 注册第三方 MCP server,API key 通过 `${MCC_PROVIDER_KEY:<providerId>}` 引用 `~/.mcc/mcp-config.json` 中的配置
|
|
24
|
-
- **MCP provider 配置系统**:`~/.mcc/mcp-config.json` 统一管理 WebSearch(duckduckgo/exa/tavily/brave)和 ImageAnalysis(ali/kimi/minimax/deepseek)的 provider 开关和 API key
|
|
25
|
-
- **跨 instance 共享**:skills、commands、agents、plugins、settings.json 通过 symlink 在所有 instance 间共享(`~/.claude/` → `~/.mcc/instances/<name>/<item>`)
|
|
26
|
-
- **Session 日志**:每次启动生成独立 session 日志(`~/.mcc/logs/<profile>/<sessionId>/mcc.log`),自动 logrotate
|
|
27
|
-
- **Web Dashboard**:Express + 静态文件,提供 profile/MCP/模型配置的管理界面(`npm run dashboard`,端口 3000)
|
|
28
|
-
|
|
29
|
-
## 明确不做什么
|
|
30
|
-
|
|
31
|
-
- 不做 OAuth 登录流程(账号靠 API key)
|
|
32
|
-
- 不做 Codex CLI / Factory Droid 等 runtime 桥接(只支持 Claude Code)
|
|
33
|
-
- 不做 quota 监控 / cost tracking
|
|
34
|
-
|
|
35
|
-
## 待规划
|
|
36
|
-
|
|
37
|
-
- 多个 Claude Code 官方账号切换
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Instance Manager - Account isolation via CLAUDE_CONFIG_DIR
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import { SharedManager } from './shared-manager';
|
|
8
|
-
|
|
9
|
-
function getMccHome(): string {
|
|
10
|
-
return process.env.MCC_HOME ?? path.join(process.env.HOME ?? process.env.USERPROFILE ?? '~', '.mcc');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function getInstancesDir(): string {
|
|
14
|
-
return path.join(getMccHome(), 'instances');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const sharedManager = new SharedManager();
|
|
18
|
-
|
|
19
|
-
export class MCCInstanceManager {
|
|
20
|
-
async ensureInstance(accountName: string): Promise<string> {
|
|
21
|
-
const instancePath = this.getInstancePath(accountName);
|
|
22
|
-
if (!fs.existsSync(instancePath)) {
|
|
23
|
-
fs.mkdirSync(instancePath, { recursive: true, mode: 0o700 });
|
|
24
|
-
const subdirs = ['session-env', 'todos', 'logs', 'file-history', 'shell-snapshots', 'debug', '.anthropic'];
|
|
25
|
-
for (const dir of subdirs) {
|
|
26
|
-
fs.mkdirSync(path.join(instancePath, dir), { recursive: true, mode: 0o700 });
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
// Always re-link shared directories (skills, commands, agents, plugins, settings)
|
|
30
|
-
sharedManager.linkSharedDirectories(instancePath);
|
|
31
|
-
return instancePath;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
getInstancePath(accountName: string): string {
|
|
35
|
-
const safeName = accountName.replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
|
|
36
|
-
return path.join(getInstancesDir(), safeName);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async deleteInstance(accountName: string): Promise<void> {
|
|
40
|
-
const instancePath = this.getInstancePath(accountName);
|
|
41
|
-
if (fs.existsSync(instancePath)) {
|
|
42
|
-
fs.rmSync(instancePath, { recursive: true, force: true });
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
listInstances(): string[] {
|
|
47
|
-
const instancesDir = getInstancesDir();
|
|
48
|
-
if (!fs.existsSync(instancesDir)) return [];
|
|
49
|
-
return fs.readdirSync(instancesDir).filter((name) => {
|
|
50
|
-
if (name.startsWith('.')) return false;
|
|
51
|
-
return fs.statSync(path.join(instancesDir, name)).isDirectory();
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
hasInstance(accountName: string): boolean {
|
|
56
|
-
return fs.existsSync(this.getInstancePath(accountName));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SharedManager - Symlink management for shared directories
|
|
3
|
-
* Ported from CCS management/shared-manager.js (simplified)
|
|
4
|
-
*
|
|
5
|
-
* Creates symlinks: ~/.mcc/instances/<name>/<item> -> ~/.claude/<item>
|
|
6
|
-
* so that skills, commands, agents, plugins, and settings are shared across instances.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import * as fs from 'fs';
|
|
10
|
-
import * as path from 'path';
|
|
11
|
-
import pc from 'picocolors';
|
|
12
|
-
|
|
13
|
-
function getHome(): string {
|
|
14
|
-
return process.env.HOME ?? process.env.USERPROFILE ?? '~';
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface SharedItem {
|
|
18
|
-
name: string;
|
|
19
|
-
type: 'directory' | 'file';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const SHARED_ITEMS: readonly SharedItem[] = [
|
|
23
|
-
{ name: 'commands', type: 'directory' },
|
|
24
|
-
{ name: 'skills', type: 'directory' },
|
|
25
|
-
{ name: 'agents', type: 'directory' },
|
|
26
|
-
{ name: 'plugins', type: 'directory' },
|
|
27
|
-
{ name: 'settings.json', type: 'file' },
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
export class SharedManager {
|
|
31
|
-
private claudeDir: string;
|
|
32
|
-
|
|
33
|
-
constructor() {
|
|
34
|
-
this.claudeDir = path.join(getHome(), '.claude');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Link shared directories to an instance.
|
|
39
|
-
* Creates symlinks from instance/<item> -> ~/.claude/<item>
|
|
40
|
-
*/
|
|
41
|
-
linkSharedDirectories(instancePath: string): void {
|
|
42
|
-
const linked: string[] = [];
|
|
43
|
-
for (const item of SHARED_ITEMS) {
|
|
44
|
-
const claudePath = path.join(this.claudeDir, item.name);
|
|
45
|
-
const linkPath = path.join(instancePath, item.name);
|
|
46
|
-
|
|
47
|
-
// Ensure source exists in ~/.claude/
|
|
48
|
-
if (!this.pathExists(claudePath)) {
|
|
49
|
-
if (item.type === 'directory') {
|
|
50
|
-
fs.mkdirSync(claudePath, { recursive: true, mode: 0o700 });
|
|
51
|
-
} else {
|
|
52
|
-
fs.writeFileSync(claudePath, '{}', 'utf8');
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Skip if already a symlink pointing to the correct target
|
|
57
|
-
if (this.isCorrectSymlink(linkPath, claudePath)) {
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Remove existing path (file, dir, or stale symlink)
|
|
62
|
-
this.removeExisting(linkPath, item.type);
|
|
63
|
-
|
|
64
|
-
// Create symlink
|
|
65
|
-
try {
|
|
66
|
-
const symlinkType = item.type === 'directory' ? 'dir' : 'file';
|
|
67
|
-
fs.symlinkSync(claudePath, linkPath, symlinkType);
|
|
68
|
-
linked.push(item.name);
|
|
69
|
-
} catch (_err) {
|
|
70
|
-
// Windows fallback: copy if symlink fails (Developer Mode not enabled)
|
|
71
|
-
if (process.platform === 'win32') {
|
|
72
|
-
if (item.type === 'directory') {
|
|
73
|
-
this.copyDirectoryFallback(claudePath, linkPath);
|
|
74
|
-
} else {
|
|
75
|
-
fs.copyFileSync(claudePath, linkPath);
|
|
76
|
-
}
|
|
77
|
-
console.warn(`[!] Symlink failed for ${item.name}, copied instead (enable Developer Mode for symlinks)`);
|
|
78
|
-
linked.push(`${item.name} (copied)`);
|
|
79
|
-
} else {
|
|
80
|
-
throw _err;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (linked.length > 0) {
|
|
85
|
-
console.log(` ${pc.dim('shared')} ${pc.dim(linked.join(', '))}`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Remove symlinks from an instance (cleanup).
|
|
91
|
-
*/
|
|
92
|
-
unlinkSharedDirectories(instancePath: string): void {
|
|
93
|
-
for (const item of SHARED_ITEMS) {
|
|
94
|
-
const linkPath = path.join(instancePath, item.name);
|
|
95
|
-
if (!this.pathExists(linkPath)) continue;
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const stats = fs.lstatSync(linkPath);
|
|
99
|
-
if (stats.isSymbolicLink()) {
|
|
100
|
-
fs.unlinkSync(linkPath);
|
|
101
|
-
}
|
|
102
|
-
} catch {
|
|
103
|
-
// Best-effort
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private pathExists(p: string): boolean {
|
|
109
|
-
try {
|
|
110
|
-
fs.lstatSync(p);
|
|
111
|
-
return true;
|
|
112
|
-
} catch {
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private isCorrectSymlink(linkPath: string, expectedTarget: string): boolean {
|
|
118
|
-
try {
|
|
119
|
-
const stats = fs.lstatSync(linkPath);
|
|
120
|
-
if (!stats.isSymbolicLink()) return false;
|
|
121
|
-
const currentTarget = fs.readlinkSync(linkPath);
|
|
122
|
-
const resolved = path.resolve(path.dirname(linkPath), currentTarget);
|
|
123
|
-
return resolved === expectedTarget;
|
|
124
|
-
} catch {
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
private removeExisting(p: string, type: 'directory' | 'file'): void {
|
|
130
|
-
if (!this.pathExists(p)) return;
|
|
131
|
-
if (type === 'directory') {
|
|
132
|
-
fs.rmSync(p, { recursive: true, force: true });
|
|
133
|
-
} else {
|
|
134
|
-
try {
|
|
135
|
-
fs.unlinkSync(p);
|
|
136
|
-
} catch {
|
|
137
|
-
fs.rmSync(p, { force: true });
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
private copyDirectoryFallback(src: string, dest: string): void {
|
|
143
|
-
fs.mkdirSync(dest, { recursive: true, mode: 0o700 });
|
|
144
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
145
|
-
const srcPath = path.join(src, entry.name);
|
|
146
|
-
const destPath = path.join(dest, entry.name);
|
|
147
|
-
if (entry.isDirectory()) {
|
|
148
|
-
this.copyDirectoryFallback(srcPath, destPath);
|
|
149
|
-
} else {
|
|
150
|
-
fs.copyFileSync(srcPath, destPath);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|