mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
feat: add CLI Command Node and Prompt Node components for orchestrator
- Implemented CliCommandNode component for executing CLI tools with AI models. - Implemented PromptNode component for constructing AI prompts with context. - Added styling for mode and tool badges in both components. - Enhanced user experience with command and argument previews, execution status, and error handling. test: add comprehensive tests for ask_question tool - Created direct test for ask_question tool execution. - Developed end-to-end tests to validate ask_question tool integration with WebSocket and A2UI surfaces. - Implemented simple and integrated WebSocket tests to ensure proper message handling and surface reception. - Added tool registration test to verify ask_question tool is correctly registered. chore: add WebSocket listener and simulation tests - Added WebSocket listener for A2UI surfaces to facilitate testing. - Implemented frontend simulation test to validate complete flow from backend to frontend. - Created various test scripts to ensure robust testing of ask_question tool functionality.
This commit is contained in:
@@ -143,225 +143,225 @@
|
||||
"11": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/ea313555.4eea9e04.js",
|
||||
"hash": "d5b66e7ebe350f71",
|
||||
"publicPath": "/docs/assets/js/ea313555.4eea9e04.js"
|
||||
"file": "assets/js/ea313555.5ab441e2.js",
|
||||
"hash": "31df2fb9fdf53a9c",
|
||||
"publicPath": "/docs/assets/js/ea313555.5ab441e2.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"17": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/ccef5d0f.421f57e1.js",
|
||||
"hash": "021994e665cd71a3",
|
||||
"publicPath": "/docs/assets/js/ccef5d0f.421f57e1.js"
|
||||
"file": "assets/js/ccef5d0f.265182f6.js",
|
||||
"hash": "6c0ff2dcaa768308",
|
||||
"publicPath": "/docs/assets/js/ccef5d0f.265182f6.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"47": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/157db180.4fb84679.js",
|
||||
"hash": "57827c9137698781",
|
||||
"publicPath": "/docs/assets/js/157db180.4fb84679.js"
|
||||
"file": "assets/js/157db180.600b1451.js",
|
||||
"hash": "101173fa95bef712",
|
||||
"publicPath": "/docs/assets/js/157db180.600b1451.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"48": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/a94703ab.5b347b84.js",
|
||||
"hash": "b41b6704ee40e61e",
|
||||
"publicPath": "/docs/assets/js/a94703ab.5b347b84.js"
|
||||
"file": "assets/js/a94703ab.7b43e8e3.js",
|
||||
"hash": "3d3895729cce8c0b",
|
||||
"publicPath": "/docs/assets/js/a94703ab.7b43e8e3.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"57": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/19b64556.a6163a0a.js",
|
||||
"hash": "5ee521c37cf8c68a",
|
||||
"publicPath": "/docs/assets/js/19b64556.a6163a0a.js"
|
||||
"file": "assets/js/19b64556.6a97ef6e.js",
|
||||
"hash": "d97b180da100ad3d",
|
||||
"publicPath": "/docs/assets/js/19b64556.6a97ef6e.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"98": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/a7bd4aaa.5f0b376e.js",
|
||||
"hash": "fa2f4121247f1996",
|
||||
"publicPath": "/docs/assets/js/a7bd4aaa.5f0b376e.js"
|
||||
"file": "assets/js/a7bd4aaa.e4fb75f9.js",
|
||||
"hash": "1750b84b0b2f61c9",
|
||||
"publicPath": "/docs/assets/js/a7bd4aaa.e4fb75f9.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"121": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/5c7b2278.0eaf76aa.js",
|
||||
"hash": "5c09d012d0631523",
|
||||
"publicPath": "/docs/assets/js/5c7b2278.0eaf76aa.js"
|
||||
"file": "assets/js/5c7b2278.20942c2c.js",
|
||||
"hash": "43cd1bc77a737f87",
|
||||
"publicPath": "/docs/assets/js/5c7b2278.20942c2c.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"142": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/0566a0a8.13f3324d.js",
|
||||
"hash": "aab4cc959eec590f",
|
||||
"publicPath": "/docs/assets/js/0566a0a8.13f3324d.js"
|
||||
"file": "assets/js/0566a0a8.9fc6236d.js",
|
||||
"hash": "f4c3f0f2338daebf",
|
||||
"publicPath": "/docs/assets/js/0566a0a8.9fc6236d.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"148": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/4cc74730.4af80d66.js",
|
||||
"hash": "3fdd0970d9784df1",
|
||||
"publicPath": "/docs/assets/js/4cc74730.4af80d66.js"
|
||||
"file": "assets/js/4cc74730.d5719b6e.js",
|
||||
"hash": "2bbf159dbb02f1bf",
|
||||
"publicPath": "/docs/assets/js/4cc74730.d5719b6e.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"235": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/18891827.d20470dc.js",
|
||||
"hash": "1b4aca9ca7af9b42",
|
||||
"publicPath": "/docs/assets/js/18891827.d20470dc.js"
|
||||
"file": "assets/js/18891827.f868bfb7.js",
|
||||
"hash": "340749ed34d4a80c",
|
||||
"publicPath": "/docs/assets/js/18891827.f868bfb7.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"241": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/d045285b.73fb2bfa.js",
|
||||
"hash": "f921efda005636a3",
|
||||
"publicPath": "/docs/assets/js/d045285b.73fb2bfa.js"
|
||||
"file": "assets/js/d045285b.7af76fbc.js",
|
||||
"hash": "7697f3aeebdae7e7",
|
||||
"publicPath": "/docs/assets/js/d045285b.7af76fbc.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"268": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/60eef997.a1401c2f.js",
|
||||
"hash": "eff0d2e8f0ec97a4",
|
||||
"publicPath": "/docs/assets/js/60eef997.a1401c2f.js"
|
||||
"file": "assets/js/60eef997.c3e43e3b.js",
|
||||
"hash": "cd7320a609e650d9",
|
||||
"publicPath": "/docs/assets/js/60eef997.c3e43e3b.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"288": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/666bb1bf.2da41127.js",
|
||||
"hash": "cbbe85931a51b576",
|
||||
"publicPath": "/docs/assets/js/666bb1bf.2da41127.js"
|
||||
"file": "assets/js/666bb1bf.3f6acfd8.js",
|
||||
"hash": "9cbbcf4a5813914a",
|
||||
"publicPath": "/docs/assets/js/666bb1bf.3f6acfd8.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"354": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/runtime~main.cbfbf29d.js",
|
||||
"hash": "fb4e23ef19ceb840",
|
||||
"publicPath": "/docs/assets/js/runtime~main.cbfbf29d.js"
|
||||
"file": "assets/js/runtime~main.0220a52f.js",
|
||||
"hash": "d949ea7ec67f3b25",
|
||||
"publicPath": "/docs/assets/js/runtime~main.0220a52f.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"368": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/186dcf4e.dc0319e0.js",
|
||||
"hash": "48652c8965138728",
|
||||
"publicPath": "/docs/assets/js/186dcf4e.dc0319e0.js"
|
||||
"file": "assets/js/186dcf4e.9cc2830f.js",
|
||||
"hash": "60c7326a40430bc0",
|
||||
"publicPath": "/docs/assets/js/186dcf4e.9cc2830f.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"401": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/17896441.a28ce98e.js",
|
||||
"hash": "ee3a31e7129b992e",
|
||||
"publicPath": "/docs/assets/js/17896441.a28ce98e.js"
|
||||
"file": "assets/js/17896441.d1575d23.js",
|
||||
"hash": "056571beacec7d2b",
|
||||
"publicPath": "/docs/assets/js/17896441.d1575d23.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"407": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/611877e1.71e7b278.js",
|
||||
"hash": "e3e49d37d33343ec",
|
||||
"publicPath": "/docs/assets/js/611877e1.71e7b278.js"
|
||||
"file": "assets/js/611877e1.94144df0.js",
|
||||
"hash": "adb9cfedfa03ab25",
|
||||
"publicPath": "/docs/assets/js/611877e1.94144df0.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"411": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/d550a629.41f98adf.js",
|
||||
"hash": "17b3e1cb172e9417",
|
||||
"publicPath": "/docs/assets/js/d550a629.41f98adf.js"
|
||||
"file": "assets/js/d550a629.236e0027.js",
|
||||
"hash": "8109cd68401a7340",
|
||||
"publicPath": "/docs/assets/js/d550a629.236e0027.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"412": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/1bac9067.2e1f90de.js",
|
||||
"hash": "e7eb745cc150b2db",
|
||||
"publicPath": "/docs/assets/js/1bac9067.2e1f90de.js"
|
||||
"file": "assets/js/1bac9067.44948c57.js",
|
||||
"hash": "6debf5397e2de625",
|
||||
"publicPath": "/docs/assets/js/1bac9067.44948c57.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"448": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/f9222419.eca02b22.js",
|
||||
"hash": "f234731674701f32",
|
||||
"publicPath": "/docs/assets/js/f9222419.eca02b22.js"
|
||||
"file": "assets/js/f9222419.9eaf88b7.js",
|
||||
"hash": "8e4894a08196789e",
|
||||
"publicPath": "/docs/assets/js/f9222419.9eaf88b7.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"482": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/c5a82d8d.c941bbe4.js",
|
||||
"hash": "d50445120d59b52e",
|
||||
"publicPath": "/docs/assets/js/c5a82d8d.c941bbe4.js"
|
||||
"file": "assets/js/c5a82d8d.a992435d.js",
|
||||
"hash": "70a5ce3c390104ed",
|
||||
"publicPath": "/docs/assets/js/c5a82d8d.a992435d.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"511": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/bdb2b105.00be57de.js",
|
||||
"hash": "9d2397472ac7d311",
|
||||
"publicPath": "/docs/assets/js/bdb2b105.00be57de.js"
|
||||
"file": "assets/js/bdb2b105.836bfb71.js",
|
||||
"hash": "be891f8f33c5de75",
|
||||
"publicPath": "/docs/assets/js/bdb2b105.836bfb71.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"647": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/5e95c892.1a07266b.js",
|
||||
"hash": "e8bf4ae800fb0d69",
|
||||
"publicPath": "/docs/assets/js/5e95c892.1a07266b.js"
|
||||
"file": "assets/js/5e95c892.9f686774.js",
|
||||
"hash": "bacf33c2a5027f89",
|
||||
"publicPath": "/docs/assets/js/5e95c892.9f686774.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"723": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/723.b1cb938e.js",
|
||||
"hash": "fad03893d4e20c0c",
|
||||
"publicPath": "/docs/assets/js/723.b1cb938e.js"
|
||||
"file": "assets/js/723.50d99bed.js",
|
||||
"hash": "b33d5e5ea9c9e39b",
|
||||
"publicPath": "/docs/assets/js/723.50d99bed.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"725": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/bcf6b37c.19ccccf7.js",
|
||||
"hash": "5de43b0994778155",
|
||||
"publicPath": "/docs/assets/js/bcf6b37c.19ccccf7.js"
|
||||
"file": "assets/js/bcf6b37c.b5f365c1.js",
|
||||
"hash": "e3cdd7da36380223",
|
||||
"publicPath": "/docs/assets/js/bcf6b37c.b5f365c1.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -377,54 +377,54 @@
|
||||
"777": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/fabaf1c8.811090ab.js",
|
||||
"hash": "810829664615fe67",
|
||||
"publicPath": "/docs/assets/js/fabaf1c8.811090ab.js"
|
||||
"file": "assets/js/fabaf1c8.4dc921e5.js",
|
||||
"hash": "9b5b3697ae20b25a",
|
||||
"publicPath": "/docs/assets/js/fabaf1c8.4dc921e5.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"792": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/main.6c671094.js",
|
||||
"hash": "71c89960e4f3a13b",
|
||||
"publicPath": "/docs/assets/js/main.6c671094.js"
|
||||
"file": "assets/js/main.d2194b90.js",
|
||||
"hash": "4f68ab128930f591",
|
||||
"publicPath": "/docs/assets/js/main.d2194b90.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"814": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/97c6e66a.baa03fb6.js",
|
||||
"hash": "7b555aaa8c9c205b",
|
||||
"publicPath": "/docs/assets/js/97c6e66a.baa03fb6.js"
|
||||
"file": "assets/js/97c6e66a.c0137c74.js",
|
||||
"hash": "330ed6da996e2648",
|
||||
"publicPath": "/docs/assets/js/97c6e66a.c0137c74.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"816": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/a2065270.088080b0.js",
|
||||
"hash": "d1673e1a1d2d132c",
|
||||
"publicPath": "/docs/assets/js/a2065270.088080b0.js"
|
||||
"file": "assets/js/a2065270.5d0fec0e.js",
|
||||
"hash": "3cc216d414dd2db1",
|
||||
"publicPath": "/docs/assets/js/a2065270.5d0fec0e.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"849": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/4ad7db0f.62c9b350.js",
|
||||
"hash": "24ee107279fdd24b",
|
||||
"publicPath": "/docs/assets/js/4ad7db0f.62c9b350.js"
|
||||
"file": "assets/js/4ad7db0f.fdce606b.js",
|
||||
"hash": "f6948d2effa709f2",
|
||||
"publicPath": "/docs/assets/js/4ad7db0f.fdce606b.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"856": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/2ecf8b4a.9d518d70.js",
|
||||
"hash": "54afc84cc90bb5da",
|
||||
"publicPath": "/docs/assets/js/2ecf8b4a.9d518d70.js"
|
||||
"file": "assets/js/2ecf8b4a.b5d4721e.js",
|
||||
"hash": "5c3f242aa2d54cce",
|
||||
"publicPath": "/docs/assets/js/2ecf8b4a.b5d4721e.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -440,72 +440,72 @@
|
||||
"896": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/f4817052.a5126a55.js",
|
||||
"hash": "4a5f92afb4ea9e36",
|
||||
"publicPath": "/docs/assets/js/f4817052.a5126a55.js"
|
||||
"file": "assets/js/f4817052.e0e6bfe1.js",
|
||||
"hash": "0a7875126f6eb2c1",
|
||||
"publicPath": "/docs/assets/js/f4817052.e0e6bfe1.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"927": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/04db0a2e.2c353bc4.js",
|
||||
"hash": "3fbd2a6b8ef7f3aa",
|
||||
"publicPath": "/docs/assets/js/04db0a2e.2c353bc4.js"
|
||||
"file": "assets/js/04db0a2e.0694cac9.js",
|
||||
"hash": "806ada030a424ee0",
|
||||
"publicPath": "/docs/assets/js/04db0a2e.0694cac9.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"934": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/7a1ee27c.67535332.js",
|
||||
"hash": "026327af470fe170",
|
||||
"publicPath": "/docs/assets/js/7a1ee27c.67535332.js"
|
||||
"file": "assets/js/7a1ee27c.7f87648d.js",
|
||||
"hash": "9463b2305c127bf9",
|
||||
"publicPath": "/docs/assets/js/7a1ee27c.7f87648d.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"954": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/f1bf82ec.e2a6e8e1.js",
|
||||
"hash": "b279ee703b392c9e",
|
||||
"publicPath": "/docs/assets/js/f1bf82ec.e2a6e8e1.js"
|
||||
"file": "assets/js/f1bf82ec.1157ef45.js",
|
||||
"hash": "d583ff861727b4e6",
|
||||
"publicPath": "/docs/assets/js/f1bf82ec.1157ef45.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"971": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/fe8e3dcf.0a78caef.js",
|
||||
"hash": "e53c59fbda09ba89",
|
||||
"publicPath": "/docs/assets/js/fe8e3dcf.0a78caef.js"
|
||||
"file": "assets/js/fe8e3dcf.548de575.js",
|
||||
"hash": "194eb5369c41e318",
|
||||
"publicPath": "/docs/assets/js/fe8e3dcf.548de575.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"973": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/9f4ca91e.ea7dd9f3.js",
|
||||
"hash": "ef0810bac3ec6788",
|
||||
"publicPath": "/docs/assets/js/9f4ca91e.ea7dd9f3.js"
|
||||
"file": "assets/js/9f4ca91e.8efe3ed3.js",
|
||||
"hash": "740ae00416e616a8",
|
||||
"publicPath": "/docs/assets/js/9f4ca91e.8efe3ed3.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"975": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/1e3006f3.6141b6ef.js",
|
||||
"hash": "71a6d2b43ed65f91",
|
||||
"publicPath": "/docs/assets/js/1e3006f3.6141b6ef.js"
|
||||
"file": "assets/js/1e3006f3.9dddfb7c.js",
|
||||
"hash": "81f2929933008168",
|
||||
"publicPath": "/docs/assets/js/1e3006f3.9dddfb7c.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"991": {
|
||||
"js": [
|
||||
{
|
||||
"file": "assets/js/a6c3df16.fddd53e1.js",
|
||||
"hash": "ae93c9b20718d69b",
|
||||
"publicPath": "/docs/assets/js/a6c3df16.fddd53e1.js"
|
||||
"file": "assets/js/a6c3df16.7cfa0c0a.js",
|
||||
"hash": "c77cba7a283c5817",
|
||||
"publicPath": "/docs/assets/js/a6c3df16.7cfa0c0a.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,46 +1,38 @@
|
||||
export default {
|
||||
"__comp---theme-debug-config-23-a-2ff": [() => import(/* webpackChunkName: "__comp---theme-debug-config-23-a-2ff" */ "@theme/DebugConfig"), "@theme/DebugConfig", require.resolveWeak("@theme/DebugConfig")],
|
||||
"__comp---theme-debug-contentba-8-ce7": [() => import(/* webpackChunkName: "__comp---theme-debug-contentba-8-ce7" */ "@theme/DebugContent"), "@theme/DebugContent", require.resolveWeak("@theme/DebugContent")],
|
||||
"__comp---theme-debug-global-dataede-0fa": [() => import(/* webpackChunkName: "__comp---theme-debug-global-dataede-0fa" */ "@theme/DebugGlobalData"), "@theme/DebugGlobalData", require.resolveWeak("@theme/DebugGlobalData")],
|
||||
"__comp---theme-debug-registry-679-501": [() => import(/* webpackChunkName: "__comp---theme-debug-registry-679-501" */ "@theme/DebugRegistry"), "@theme/DebugRegistry", require.resolveWeak("@theme/DebugRegistry")],
|
||||
"__comp---theme-debug-routes-946-699": [() => import(/* webpackChunkName: "__comp---theme-debug-routes-946-699" */ "@theme/DebugRoutes"), "@theme/DebugRoutes", require.resolveWeak("@theme/DebugRoutes")],
|
||||
"__comp---theme-debug-site-metadata-68-e-3d4": [() => import(/* webpackChunkName: "__comp---theme-debug-site-metadata-68-e-3d4" */ "@theme/DebugSiteMetadata"), "@theme/DebugSiteMetadata", require.resolveWeak("@theme/DebugSiteMetadata")],
|
||||
"__comp---theme-doc-item-178-a40": [() => import(/* webpackChunkName: "__comp---theme-doc-item-178-a40" */ "@theme/DocItem"), "@theme/DocItem", require.resolveWeak("@theme/DocItem")],
|
||||
"__comp---theme-doc-roota-94-67a": [() => import(/* webpackChunkName: "__comp---theme-doc-roota-94-67a" */ "@theme/DocRoot"), "@theme/DocRoot", require.resolveWeak("@theme/DocRoot")],
|
||||
"__comp---theme-doc-version-roota-7-b-5de": [() => import(/* webpackChunkName: "__comp---theme-doc-version-roota-7-b-5de" */ "@theme/DocVersionRoot"), "@theme/DocVersionRoot", require.resolveWeak("@theme/DocVersionRoot")],
|
||||
"__comp---theme-docs-root-5-e-9-0b6": [() => import(/* webpackChunkName: "__comp---theme-docs-root-5-e-9-0b6" */ "@theme/DocsRoot"), "@theme/DocsRoot", require.resolveWeak("@theme/DocsRoot")],
|
||||
"__props---docs-docsa-20-f19": [() => import(/* webpackChunkName: "__props---docs-docsa-20-f19" */ "@generated/docusaurus-plugin-content-docs/default/p/docs-docs-fbb.json"), "@generated/docusaurus-plugin-content-docs/default/p/docs-docs-fbb.json", require.resolveWeak("@generated/docusaurus-plugin-content-docs/default/p/docs-docs-fbb.json")],
|
||||
"__props---docs-docusaurus-debug-content-344-8d5": [() => import(/* webpackChunkName: "__props---docs-docusaurus-debug-content-344-8d5" */ "@generated/docusaurus-plugin-debug/default/p/docs-docusaurus-debug-content-a52.json"), "@generated/docusaurus-plugin-debug/default/p/docs-docusaurus-debug-content-a52.json", require.resolveWeak("@generated/docusaurus-plugin-debug/default/p/docs-docusaurus-debug-content-a52.json")],
|
||||
"content---docs-docs-commands-cli-cli-init-056-2d3": [() => import(/* webpackChunkName: "content---docs-docs-commands-cli-cli-init-056-2d3" */ "@site/docs/commands/cli/cli-init.mdx"), "@site/docs/commands/cli/cli-init.mdx", require.resolveWeak("@site/docs/commands/cli/cli-init.mdx")],
|
||||
"content---docs-docs-commands-cli-codex-reviewf-1-b-532": [() => import(/* webpackChunkName: "content---docs-docs-commands-cli-codex-reviewf-1-b-532" */ "@site/docs/commands/cli/codex-review.mdx"), "@site/docs/commands/cli/codex-review.mdx", require.resolveWeak("@site/docs/commands/cli/codex-review.mdx")],
|
||||
"content---docs-docs-commands-general-ccw-coordinatord-55-a04": [() => import(/* webpackChunkName: "content---docs-docs-commands-general-ccw-coordinatord-55-a04" */ "@site/docs/commands/general/ccw-coordinator.mdx"), "@site/docs/commands/general/ccw-coordinator.mdx", require.resolveWeak("@site/docs/commands/general/ccw-coordinator.mdx")],
|
||||
"content---docs-docs-commands-general-ccw-debug-97-c-2c8": [() => import(/* webpackChunkName: "content---docs-docs-commands-general-ccw-debug-97-c-2c8" */ "@site/docs/commands/general/ccw-debug.mdx"), "@site/docs/commands/general/ccw-debug.mdx", require.resolveWeak("@site/docs/commands/general/ccw-debug.mdx")],
|
||||
"content---docs-docs-commands-general-ccw-plan-04-d-aa3": [() => import(/* webpackChunkName: "content---docs-docs-commands-general-ccw-plan-04-d-aa3" */ "@site/docs/commands/general/ccw-plan.mdx"), "@site/docs/commands/general/ccw-plan.mdx", require.resolveWeak("@site/docs/commands/general/ccw-plan.mdx")],
|
||||
"content---docs-docs-commands-general-ccw-testcce-a5d": [() => import(/* webpackChunkName: "content---docs-docs-commands-general-ccw-testcce-a5d" */ "@site/docs/commands/general/ccw-test.mdx"), "@site/docs/commands/general/ccw-test.mdx", require.resolveWeak("@site/docs/commands/general/ccw-test.mdx")],
|
||||
"content---docs-docs-commands-general-ccwf-48-4f9": [() => import(/* webpackChunkName: "content---docs-docs-commands-general-ccwf-48-4f9" */ "@site/docs/commands/general/ccw.mdx"), "@site/docs/commands/general/ccw.mdx", require.resolveWeak("@site/docs/commands/general/ccw.mdx")],
|
||||
"content---docs-docs-commands-general-codex-coordinatorf-92-965": [() => import(/* webpackChunkName: "content---docs-docs-commands-general-codex-coordinatorf-92-965" */ "@site/docs/commands/general/codex-coordinator.mdx"), "@site/docs/commands/general/codex-coordinator.mdx", require.resolveWeak("@site/docs/commands/general/codex-coordinator.mdx")],
|
||||
"content---docs-docs-commands-general-flow-createfab-5a7": [() => import(/* webpackChunkName: "content---docs-docs-commands-general-flow-createfab-5a7" */ "@site/docs/commands/general/flow-create.mdx"), "@site/docs/commands/general/flow-create.mdx", require.resolveWeak("@site/docs/commands/general/flow-create.mdx")],
|
||||
"content---docs-docs-commands-issue-issue-convert-to-plan-5-c-7-428": [() => import(/* webpackChunkName: "content---docs-docs-commands-issue-issue-convert-to-plan-5-c-7-428" */ "@site/docs/commands/issue/issue-convert-to-plan.md"), "@site/docs/commands/issue/issue-convert-to-plan.md", require.resolveWeak("@site/docs/commands/issue/issue-convert-to-plan.md")],
|
||||
"content---docs-docs-commands-issue-issue-discover-1-e-3-61d": [() => import(/* webpackChunkName: "content---docs-docs-commands-issue-issue-discover-1-e-3-61d" */ "@site/docs/commands/issue/issue-discover.md"), "@site/docs/commands/issue/issue-discover.md", require.resolveWeak("@site/docs/commands/issue/issue-discover.md")],
|
||||
"content---docs-docs-commands-issue-issue-executefe-8-121": [() => import(/* webpackChunkName: "content---docs-docs-commands-issue-issue-executefe-8-121" */ "@site/docs/commands/issue/issue-execute.md"), "@site/docs/commands/issue/issue-execute.md", require.resolveWeak("@site/docs/commands/issue/issue-execute.md")],
|
||||
"content---docs-docs-commands-issue-issue-from-brainstorm-2-ec-ca5": [() => import(/* webpackChunkName: "content---docs-docs-commands-issue-issue-from-brainstorm-2-ec-ca5" */ "@site/docs/commands/issue/issue-from-brainstorm.md"), "@site/docs/commands/issue/issue-from-brainstorm.md", require.resolveWeak("@site/docs/commands/issue/issue-from-brainstorm.md")],
|
||||
"content---docs-docs-commands-issue-issue-new-4-ad-afb": [() => import(/* webpackChunkName: "content---docs-docs-commands-issue-issue-new-4-ad-afb" */ "@site/docs/commands/issue/issue-new.md"), "@site/docs/commands/issue/issue-new.md", require.resolveWeak("@site/docs/commands/issue/issue-new.md")],
|
||||
"content---docs-docs-commands-issue-issue-plana-6-c-1b0": [() => import(/* webpackChunkName: "content---docs-docs-commands-issue-issue-plana-6-c-1b0" */ "@site/docs/commands/issue/issue-plan.md"), "@site/docs/commands/issue/issue-plan.md", require.resolveWeak("@site/docs/commands/issue/issue-plan.md")],
|
||||
"content---docs-docs-commands-issue-issue-queue-1-ba-06f": [() => import(/* webpackChunkName: "content---docs-docs-commands-issue-issue-queue-1-ba-06f" */ "@site/docs/commands/issue/issue-queue.md"), "@site/docs/commands/issue/issue-queue.md", require.resolveWeak("@site/docs/commands/issue/issue-queue.md")],
|
||||
"content---docs-docs-commands-memory-memory-compact-7-a-1-f85": [() => import(/* webpackChunkName: "content---docs-docs-commands-memory-memory-compact-7-a-1-f85" */ "@site/docs/commands/memory/memory-compact.mdx"), "@site/docs/commands/memory/memory-compact.mdx", require.resolveWeak("@site/docs/commands/memory/memory-compact.mdx")],
|
||||
"content---docs-docs-commands-memory-memory-docs-full-cli-4-cc-b15": [() => import(/* webpackChunkName: "content---docs-docs-commands-memory-memory-docs-full-cli-4-cc-b15" */ "@site/docs/commands/memory/memory-docs-full-cli.mdx"), "@site/docs/commands/memory/memory-docs-full-cli.mdx", require.resolveWeak("@site/docs/commands/memory/memory-docs-full-cli.mdx")],
|
||||
"content---docs-docs-commands-memory-memory-docs-related-cli-60-e-28a": [() => import(/* webpackChunkName: "content---docs-docs-commands-memory-memory-docs-related-cli-60-e-28a" */ "@site/docs/commands/memory/memory-docs-related-cli.mdx"), "@site/docs/commands/memory/memory-docs-related-cli.mdx", require.resolveWeak("@site/docs/commands/memory/memory-docs-related-cli.mdx")],
|
||||
"content---docs-docs-commands-memory-memory-load-157-920": [() => import(/* webpackChunkName: "content---docs-docs-commands-memory-memory-load-157-920" */ "@site/docs/commands/memory/memory-load.mdx"), "@site/docs/commands/memory/memory-load.mdx", require.resolveWeak("@site/docs/commands/memory/memory-load.mdx")],
|
||||
"content---docs-docs-commands-memory-memory-update-full-666-28e": [() => import(/* webpackChunkName: "content---docs-docs-commands-memory-memory-update-full-666-28e" */ "@site/docs/commands/memory/memory-update-full.mdx"), "@site/docs/commands/memory/memory-update-full.mdx", require.resolveWeak("@site/docs/commands/memory/memory-update-full.mdx")],
|
||||
"content---docs-docs-commands-memory-memory-update-related-611-f0a": [() => import(/* webpackChunkName: "content---docs-docs-commands-memory-memory-update-related-611-f0a" */ "@site/docs/commands/memory/memory-update-related.mdx"), "@site/docs/commands/memory/memory-update-related.mdx", require.resolveWeak("@site/docs/commands/memory/memory-update-related.mdx")],
|
||||
"content---docs-docs-faqea-3-29f": [() => import(/* webpackChunkName: "content---docs-docs-faqea-3-29f" */ "@site/docs/faq.mdx"), "@site/docs/faq.mdx", require.resolveWeak("@site/docs/faq.mdx")],
|
||||
"content---docs-docs-overview-188-8fe": [() => import(/* webpackChunkName: "content---docs-docs-overview-188-8fe" */ "@site/docs/overview.mdx"), "@site/docs/overview.mdx", require.resolveWeak("@site/docs/overview.mdx")],
|
||||
"content---docs-docs-workflows-faqbcf-a47": [() => import(/* webpackChunkName: "content---docs-docs-workflows-faqbcf-a47" */ "@site/docs/workflows/faq.mdx"), "@site/docs/workflows/faq.mdx", require.resolveWeak("@site/docs/workflows/faq.mdx")],
|
||||
"content---docs-docs-workflows-introduction-9-f-4-dba": [() => import(/* webpackChunkName: "content---docs-docs-workflows-introduction-9-f-4-dba" */ "@site/docs/workflows/introduction.mdx"), "@site/docs/workflows/introduction.mdx", require.resolveWeak("@site/docs/workflows/introduction.mdx")],
|
||||
"content---docs-docs-workflows-level-1-ultra-lightweightc-5-a-692": [() => import(/* webpackChunkName: "content---docs-docs-workflows-level-1-ultra-lightweightc-5-a-692" */ "@site/docs/workflows/level-1-ultra-lightweight.mdx"), "@site/docs/workflows/level-1-ultra-lightweight.mdx", require.resolveWeak("@site/docs/workflows/level-1-ultra-lightweight.mdx")],
|
||||
"content---docs-docs-workflows-level-2-rapid-19-b-a2f": [() => import(/* webpackChunkName: "content---docs-docs-workflows-level-2-rapid-19-b-a2f" */ "@site/docs/workflows/level-2-rapid.mdx"), "@site/docs/workflows/level-2-rapid.mdx", require.resolveWeak("@site/docs/workflows/level-2-rapid.mdx")],
|
||||
"content---docs-docs-workflows-level-3-standardbdb-19a": [() => import(/* webpackChunkName: "content---docs-docs-workflows-level-3-standardbdb-19a" */ "@site/docs/workflows/level-3-standard.mdx"), "@site/docs/workflows/level-3-standard.mdx", require.resolveWeak("@site/docs/workflows/level-3-standard.mdx")],
|
||||
"content---docs-docs-workflows-level-4-brainstormd-04-69a": [() => import(/* webpackChunkName: "content---docs-docs-workflows-level-4-brainstormd-04-69a" */ "@site/docs/workflows/level-4-brainstorm.mdx"), "@site/docs/workflows/level-4-brainstorm.mdx", require.resolveWeak("@site/docs/workflows/level-4-brainstorm.mdx")],
|
||||
"content---docs-docs-workflows-level-5-intelligent-186-435": [() => import(/* webpackChunkName: "content---docs-docs-workflows-level-5-intelligent-186-435" */ "@site/docs/workflows/level-5-intelligent.mdx"), "@site/docs/workflows/level-5-intelligent.mdx", require.resolveWeak("@site/docs/workflows/level-5-intelligent.mdx")],
|
||||
"plugin---docs-docsaba-31e": [() => import(/* webpackChunkName: "plugin---docs-docsaba-31e" */ "@generated/docusaurus-plugin-content-docs/default/__plugin.json"), "@generated/docusaurus-plugin-content-docs/default/__plugin.json", require.resolveWeak("@generated/docusaurus-plugin-content-docs/default/__plugin.json")],
|
||||
"plugin---docs-docusaurus-debugb-38-c84": [() => import(/* webpackChunkName: "plugin---docs-docusaurus-debugb-38-c84" */ "@generated/docusaurus-plugin-debug/default/__plugin.json"), "@generated/docusaurus-plugin-debug/default/__plugin.json", require.resolveWeak("@generated/docusaurus-plugin-debug/default/__plugin.json")],};
|
||||
"04db0a2e": [() => import(/* webpackChunkName: "04db0a2e" */ "@site/docs/commands/general/ccw-plan.mdx"), "@site/docs/commands/general/ccw-plan.mdx", require.resolveWeak("@site/docs/commands/general/ccw-plan.mdx")],
|
||||
"0566a0a8": [() => import(/* webpackChunkName: "0566a0a8" */ "@site/docs/commands/cli/cli-init.mdx"), "@site/docs/commands/cli/cli-init.mdx", require.resolveWeak("@site/docs/commands/cli/cli-init.mdx")],
|
||||
"157db180": [() => import(/* webpackChunkName: "157db180" */ "@site/docs/commands/memory/memory-load.mdx"), "@site/docs/commands/memory/memory-load.mdx", require.resolveWeak("@site/docs/commands/memory/memory-load.mdx")],
|
||||
"17896441": [() => import(/* webpackChunkName: "17896441" */ "@theme/DocItem"), "@theme/DocItem", require.resolveWeak("@theme/DocItem")],
|
||||
"186dcf4e": [() => import(/* webpackChunkName: "186dcf4e" */ "@site/docs/workflows/level-5-intelligent.mdx"), "@site/docs/workflows/level-5-intelligent.mdx", require.resolveWeak("@site/docs/workflows/level-5-intelligent.mdx")],
|
||||
"18891827": [() => import(/* webpackChunkName: "18891827" */ "@site/docs/overview.mdx"), "@site/docs/overview.mdx", require.resolveWeak("@site/docs/overview.mdx")],
|
||||
"19b64556": [() => import(/* webpackChunkName: "19b64556" */ "@site/docs/workflows/level-2-rapid.mdx"), "@site/docs/workflows/level-2-rapid.mdx", require.resolveWeak("@site/docs/workflows/level-2-rapid.mdx")],
|
||||
"1bac9067": [() => import(/* webpackChunkName: "1bac9067" */ "@site/docs/commands/issue/issue-queue.md"), "@site/docs/commands/issue/issue-queue.md", require.resolveWeak("@site/docs/commands/issue/issue-queue.md")],
|
||||
"1e3006f3": [() => import(/* webpackChunkName: "1e3006f3" */ "@site/docs/commands/issue/issue-discover.md"), "@site/docs/commands/issue/issue-discover.md", require.resolveWeak("@site/docs/commands/issue/issue-discover.md")],
|
||||
"2ecf8b4a": [() => import(/* webpackChunkName: "2ecf8b4a" */ "@site/docs/commands/issue/issue-from-brainstorm.md"), "@site/docs/commands/issue/issue-from-brainstorm.md", require.resolveWeak("@site/docs/commands/issue/issue-from-brainstorm.md")],
|
||||
"4ad7db0f": [() => import(/* webpackChunkName: "4ad7db0f" */ "@site/docs/commands/issue/issue-new.md"), "@site/docs/commands/issue/issue-new.md", require.resolveWeak("@site/docs/commands/issue/issue-new.md")],
|
||||
"4cc74730": [() => import(/* webpackChunkName: "4cc74730" */ "@site/docs/commands/memory/memory-docs-full-cli.mdx"), "@site/docs/commands/memory/memory-docs-full-cli.mdx", require.resolveWeak("@site/docs/commands/memory/memory-docs-full-cli.mdx")],
|
||||
"5c7b2278": [() => import(/* webpackChunkName: "5c7b2278" */ "@site/docs/commands/issue/issue-convert-to-plan.md"), "@site/docs/commands/issue/issue-convert-to-plan.md", require.resolveWeak("@site/docs/commands/issue/issue-convert-to-plan.md")],
|
||||
"5e95c892": [() => import(/* webpackChunkName: "5e95c892" */ "@theme/DocsRoot"), "@theme/DocsRoot", require.resolveWeak("@theme/DocsRoot")],
|
||||
"60eef997": [() => import(/* webpackChunkName: "60eef997" */ "@site/docs/commands/memory/memory-docs-related-cli.mdx"), "@site/docs/commands/memory/memory-docs-related-cli.mdx", require.resolveWeak("@site/docs/commands/memory/memory-docs-related-cli.mdx")],
|
||||
"611877e1": [() => import(/* webpackChunkName: "611877e1" */ "@site/docs/commands/memory/memory-update-related.mdx"), "@site/docs/commands/memory/memory-update-related.mdx", require.resolveWeak("@site/docs/commands/memory/memory-update-related.mdx")],
|
||||
"666bb1bf": [() => import(/* webpackChunkName: "666bb1bf" */ "@site/docs/commands/memory/memory-update-full.mdx"), "@site/docs/commands/memory/memory-update-full.mdx", require.resolveWeak("@site/docs/commands/memory/memory-update-full.mdx")],
|
||||
"7a1ee27c": [() => import(/* webpackChunkName: "7a1ee27c" */ "@site/docs/commands/memory/memory-compact.mdx"), "@site/docs/commands/memory/memory-compact.mdx", require.resolveWeak("@site/docs/commands/memory/memory-compact.mdx")],
|
||||
"97c6e66a": [() => import(/* webpackChunkName: "97c6e66a" */ "@site/docs/commands/general/ccw-debug.mdx"), "@site/docs/commands/general/ccw-debug.mdx", require.resolveWeak("@site/docs/commands/general/ccw-debug.mdx")],
|
||||
"9f4ca91e": [() => import(/* webpackChunkName: "9f4ca91e" */ "@site/docs/workflows/introduction.mdx"), "@site/docs/workflows/introduction.mdx", require.resolveWeak("@site/docs/workflows/introduction.mdx")],
|
||||
"a2065270": [() => import(/* webpackChunkName: "a2065270" */ "@generated/docusaurus-plugin-content-docs/default/p/docs-docs-fbb.json"), "@generated/docusaurus-plugin-content-docs/default/p/docs-docs-fbb.json", require.resolveWeak("@generated/docusaurus-plugin-content-docs/default/p/docs-docs-fbb.json")],
|
||||
"a6c3df16": [() => import(/* webpackChunkName: "a6c3df16" */ "@site/docs/commands/issue/issue-plan.md"), "@site/docs/commands/issue/issue-plan.md", require.resolveWeak("@site/docs/commands/issue/issue-plan.md")],
|
||||
"a7bd4aaa": [() => import(/* webpackChunkName: "a7bd4aaa" */ "@theme/DocVersionRoot"), "@theme/DocVersionRoot", require.resolveWeak("@theme/DocVersionRoot")],
|
||||
"a94703ab": [() => import(/* webpackChunkName: "a94703ab" */ "@theme/DocRoot"), "@theme/DocRoot", require.resolveWeak("@theme/DocRoot")],
|
||||
"aba21aa0": [() => import(/* webpackChunkName: "aba21aa0" */ "@generated/docusaurus-plugin-content-docs/default/__plugin.json"), "@generated/docusaurus-plugin-content-docs/default/__plugin.json", require.resolveWeak("@generated/docusaurus-plugin-content-docs/default/__plugin.json")],
|
||||
"bcf6b37c": [() => import(/* webpackChunkName: "bcf6b37c" */ "@site/docs/workflows/faq.mdx"), "@site/docs/workflows/faq.mdx", require.resolveWeak("@site/docs/workflows/faq.mdx")],
|
||||
"bdb2b105": [() => import(/* webpackChunkName: "bdb2b105" */ "@site/docs/workflows/level-3-standard.mdx"), "@site/docs/workflows/level-3-standard.mdx", require.resolveWeak("@site/docs/workflows/level-3-standard.mdx")],
|
||||
"c5a82d8d": [() => import(/* webpackChunkName: "c5a82d8d" */ "@site/docs/workflows/level-1-ultra-lightweight.mdx"), "@site/docs/workflows/level-1-ultra-lightweight.mdx", require.resolveWeak("@site/docs/workflows/level-1-ultra-lightweight.mdx")],
|
||||
"ccef5d0f": [() => import(/* webpackChunkName: "ccef5d0f" */ "@site/docs/commands/general/ccw-test.mdx"), "@site/docs/commands/general/ccw-test.mdx", require.resolveWeak("@site/docs/commands/general/ccw-test.mdx")],
|
||||
"d045285b": [() => import(/* webpackChunkName: "d045285b" */ "@site/docs/workflows/level-4-brainstorm.mdx"), "@site/docs/workflows/level-4-brainstorm.mdx", require.resolveWeak("@site/docs/workflows/level-4-brainstorm.mdx")],
|
||||
"d550a629": [() => import(/* webpackChunkName: "d550a629" */ "@site/docs/commands/general/ccw-coordinator.mdx"), "@site/docs/commands/general/ccw-coordinator.mdx", require.resolveWeak("@site/docs/commands/general/ccw-coordinator.mdx")],
|
||||
"ea313555": [() => import(/* webpackChunkName: "ea313555" */ "@site/docs/faq.mdx"), "@site/docs/faq.mdx", require.resolveWeak("@site/docs/faq.mdx")],
|
||||
"f1bf82ec": [() => import(/* webpackChunkName: "f1bf82ec" */ "@site/docs/commands/cli/codex-review.mdx"), "@site/docs/commands/cli/codex-review.mdx", require.resolveWeak("@site/docs/commands/cli/codex-review.mdx")],
|
||||
"f4817052": [() => import(/* webpackChunkName: "f4817052" */ "@site/docs/commands/general/ccw.mdx"), "@site/docs/commands/general/ccw.mdx", require.resolveWeak("@site/docs/commands/general/ccw.mdx")],
|
||||
"f9222419": [() => import(/* webpackChunkName: "f9222419" */ "@site/docs/commands/general/codex-coordinator.mdx"), "@site/docs/commands/general/codex-coordinator.mdx", require.resolveWeak("@site/docs/commands/general/codex-coordinator.mdx")],
|
||||
"fabaf1c8": [() => import(/* webpackChunkName: "fabaf1c8" */ "@site/docs/commands/general/flow-create.mdx"), "@site/docs/commands/general/flow-create.mdx", require.resolveWeak("@site/docs/commands/general/flow-create.mdx")],
|
||||
"fe8e3dcf": [() => import(/* webpackChunkName: "fe8e3dcf" */ "@site/docs/commands/issue/issue-execute.md"), "@site/docs/commands/issue/issue-execute.md", require.resolveWeak("@site/docs/commands/issue/issue-execute.md")],};
|
||||
|
||||
@@ -2,41 +2,6 @@ import React from 'react';
|
||||
import ComponentCreator from '@docusaurus/ComponentCreator';
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/docs/__docusaurus/debug',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug', 'e58'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/__docusaurus/debug/config',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug/config', '2ce'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/__docusaurus/debug/content',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug/content', '11b'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/__docusaurus/debug/globalData',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug/globalData', 'f13'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/__docusaurus/debug/metadata',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug/metadata', 'bff'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/__docusaurus/debug/registry',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug/registry', '830'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/__docusaurus/debug/routes',
|
||||
component: ComponentCreator('/docs/__docusaurus/debug/routes', '13e'),
|
||||
exact: true
|
||||
},
|
||||
{
|
||||
path: '/docs/docs',
|
||||
component: ComponentCreator('/docs/docs', '942'),
|
||||
|
||||
@@ -1,182 +1,139 @@
|
||||
{
|
||||
"/docs/__docusaurus/debug-e58": {
|
||||
"__comp": "__comp---theme-debug-config-23-a-2ff",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
}
|
||||
},
|
||||
"/docs/__docusaurus/debug/config-2ce": {
|
||||
"__comp": "__comp---theme-debug-config-23-a-2ff",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
}
|
||||
},
|
||||
"/docs/__docusaurus/debug/content-11b": {
|
||||
"__comp": "__comp---theme-debug-contentba-8-ce7",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
},
|
||||
"__props": "__props---docs-docusaurus-debug-content-344-8d5"
|
||||
},
|
||||
"/docs/__docusaurus/debug/globalData-f13": {
|
||||
"__comp": "__comp---theme-debug-global-dataede-0fa",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
}
|
||||
},
|
||||
"/docs/__docusaurus/debug/metadata-bff": {
|
||||
"__comp": "__comp---theme-debug-site-metadata-68-e-3d4",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
}
|
||||
},
|
||||
"/docs/__docusaurus/debug/registry-830": {
|
||||
"__comp": "__comp---theme-debug-registry-679-501",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
}
|
||||
},
|
||||
"/docs/__docusaurus/debug/routes-13e": {
|
||||
"__comp": "__comp---theme-debug-routes-946-699",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docusaurus-debugb-38-c84"
|
||||
}
|
||||
},
|
||||
"/docs/docs-942": {
|
||||
"__comp": "__comp---theme-docs-root-5-e-9-0b6",
|
||||
"__comp": "5e95c892",
|
||||
"__context": {
|
||||
"plugin": "plugin---docs-docsaba-31e"
|
||||
"plugin": "aba21aa0"
|
||||
}
|
||||
},
|
||||
"/docs/docs-a90": {
|
||||
"__comp": "__comp---theme-doc-version-roota-7-b-5de",
|
||||
"__props": "__props---docs-docsa-20-f19"
|
||||
"__comp": "a7bd4aaa",
|
||||
"__props": "a2065270"
|
||||
},
|
||||
"/docs/docs-c2e": {
|
||||
"__comp": "__comp---theme-doc-roota-94-67a"
|
||||
"__comp": "a94703ab"
|
||||
},
|
||||
"/docs/docs/commands/cli/cli-init-c74": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-cli-cli-init-056-2d3"
|
||||
"__comp": "17896441",
|
||||
"content": "0566a0a8"
|
||||
},
|
||||
"/docs/docs/commands/cli/codex-review-937": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-cli-codex-reviewf-1-b-532"
|
||||
"__comp": "17896441",
|
||||
"content": "f1bf82ec"
|
||||
},
|
||||
"/docs/docs/commands/general/ccw-3fb": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-general-ccwf-48-4f9"
|
||||
"__comp": "17896441",
|
||||
"content": "f4817052"
|
||||
},
|
||||
"/docs/docs/commands/general/ccw-coordinator-a90": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-general-ccw-coordinatord-55-a04"
|
||||
"__comp": "17896441",
|
||||
"content": "d550a629"
|
||||
},
|
||||
"/docs/docs/commands/general/ccw-debug-663": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-general-ccw-debug-97-c-2c8"
|
||||
"__comp": "17896441",
|
||||
"content": "97c6e66a"
|
||||
},
|
||||
"/docs/docs/commands/general/ccw-plan-40b": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-general-ccw-plan-04-d-aa3"
|
||||
"__comp": "17896441",
|
||||
"content": "04db0a2e"
|
||||
},
|
||||
"/docs/docs/commands/general/ccw-test-99d": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-general-ccw-testcce-a5d"
|
||||
"__comp": "17896441",
|
||||
"content": "ccef5d0f"
|
||||
},
|
||||
"/docs/docs/commands/general/codex-coordinator-996": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-general-codex-coordinatorf-92-965"
|
||||
"__comp": "17896441",
|
||||
"content": "f9222419"
|
||||
},
|
||||
"/docs/docs/commands/general/flow-create-d91": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-general-flow-createfab-5a7"
|
||||
"__comp": "17896441",
|
||||
"content": "fabaf1c8"
|
||||
},
|
||||
"/docs/docs/commands/issue/issue-convert-to-plan-d90": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-issue-issue-convert-to-plan-5-c-7-428"
|
||||
"__comp": "17896441",
|
||||
"content": "5c7b2278"
|
||||
},
|
||||
"/docs/docs/commands/issue/issue-discover-2a1": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-issue-issue-discover-1-e-3-61d"
|
||||
"__comp": "17896441",
|
||||
"content": "1e3006f3"
|
||||
},
|
||||
"/docs/docs/commands/issue/issue-execute-abb": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-issue-issue-executefe-8-121"
|
||||
"__comp": "17896441",
|
||||
"content": "fe8e3dcf"
|
||||
},
|
||||
"/docs/docs/commands/issue/issue-from-brainstorm-72b": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-issue-issue-from-brainstorm-2-ec-ca5"
|
||||
"__comp": "17896441",
|
||||
"content": "2ecf8b4a"
|
||||
},
|
||||
"/docs/docs/commands/issue/issue-new-c58": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-issue-issue-new-4-ad-afb"
|
||||
"__comp": "17896441",
|
||||
"content": "4ad7db0f"
|
||||
},
|
||||
"/docs/docs/commands/issue/issue-plan-fd2": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-issue-issue-plana-6-c-1b0"
|
||||
"__comp": "17896441",
|
||||
"content": "a6c3df16"
|
||||
},
|
||||
"/docs/docs/commands/issue/issue-queue-1ce": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-issue-issue-queue-1-ba-06f"
|
||||
"__comp": "17896441",
|
||||
"content": "1bac9067"
|
||||
},
|
||||
"/docs/docs/commands/memory/memory-compact-74c": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-memory-memory-compact-7-a-1-f85"
|
||||
"__comp": "17896441",
|
||||
"content": "7a1ee27c"
|
||||
},
|
||||
"/docs/docs/commands/memory/memory-docs-full-cli-7a4": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-memory-memory-docs-full-cli-4-cc-b15"
|
||||
"__comp": "17896441",
|
||||
"content": "4cc74730"
|
||||
},
|
||||
"/docs/docs/commands/memory/memory-docs-related-cli-fb4": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-memory-memory-docs-related-cli-60-e-28a"
|
||||
"__comp": "17896441",
|
||||
"content": "60eef997"
|
||||
},
|
||||
"/docs/docs/commands/memory/memory-load-c66": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-memory-memory-load-157-920"
|
||||
"__comp": "17896441",
|
||||
"content": "157db180"
|
||||
},
|
||||
"/docs/docs/commands/memory/memory-update-full-b80": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-memory-memory-update-full-666-28e"
|
||||
"__comp": "17896441",
|
||||
"content": "666bb1bf"
|
||||
},
|
||||
"/docs/docs/commands/memory/memory-update-related-f0d": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-commands-memory-memory-update-related-611-f0a"
|
||||
"__comp": "17896441",
|
||||
"content": "611877e1"
|
||||
},
|
||||
"/docs/docs/faq-4b2": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-faqea-3-29f"
|
||||
"__comp": "17896441",
|
||||
"content": "ea313555"
|
||||
},
|
||||
"/docs/docs/overview-7df": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-overview-188-8fe"
|
||||
"__comp": "17896441",
|
||||
"content": "18891827"
|
||||
},
|
||||
"/docs/docs/workflows/faq-f47": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-workflows-faqbcf-a47"
|
||||
"__comp": "17896441",
|
||||
"content": "bcf6b37c"
|
||||
},
|
||||
"/docs/docs/workflows/introduction-4cb": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-workflows-introduction-9-f-4-dba"
|
||||
"__comp": "17896441",
|
||||
"content": "9f4ca91e"
|
||||
},
|
||||
"/docs/docs/workflows/level-1-ultra-lightweight-5c4": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-workflows-level-1-ultra-lightweightc-5-a-692"
|
||||
"__comp": "17896441",
|
||||
"content": "c5a82d8d"
|
||||
},
|
||||
"/docs/docs/workflows/level-2-rapid-ad8": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-workflows-level-2-rapid-19-b-a2f"
|
||||
"__comp": "17896441",
|
||||
"content": "19b64556"
|
||||
},
|
||||
"/docs/docs/workflows/level-3-standard-3ea": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-workflows-level-3-standardbdb-19a"
|
||||
"__comp": "17896441",
|
||||
"content": "bdb2b105"
|
||||
},
|
||||
"/docs/docs/workflows/level-4-brainstorm-f4f": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-workflows-level-4-brainstormd-04-69a"
|
||||
"__comp": "17896441",
|
||||
"content": "d045285b"
|
||||
},
|
||||
"/docs/docs/workflows/level-5-intelligent-84a": {
|
||||
"__comp": "__comp---theme-doc-item-178-a40",
|
||||
"content": "content---docs-docs-workflows-level-5-intelligent-186-435"
|
||||
"__comp": "17896441",
|
||||
"content": "186dcf4e"
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@
|
||||
"name": "@docusaurus/plugin-content-pages",
|
||||
"version": "3.9.2"
|
||||
},
|
||||
"docusaurus-plugin-debug": {
|
||||
"docusaurus-plugin-sitemap": {
|
||||
"type": "package",
|
||||
"name": "@docusaurus/plugin-debug",
|
||||
"name": "@docusaurus/plugin-sitemap",
|
||||
"version": "3.9.2"
|
||||
},
|
||||
"docusaurus-plugin-svgr": {
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="generator" content="Docusaurus v3.9.2">
|
||||
<title data-rh="true">Page Not Found | CCW Help Documentation</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:url" content="https://ccw.dev/docs/404.html"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" property="og:locale:alternate" content="zh"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docusaurus_tag" content="default"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docsearch:docusaurus_tag" content="default"><meta data-rh="true" property="og:title" content="Page Not Found | CCW Help Documentation"><link data-rh="true" rel="icon" href="/docs/img/favicon.ico"><link data-rh="true" rel="canonical" href="https://ccw.dev/docs/404.html"><link data-rh="true" rel="alternate" href="https://ccw.dev/docs/404.html" hreflang="en"><link data-rh="true" rel="alternate" href="https://ccw.dev/docs/zh/404.html" hreflang="zh"><link data-rh="true" rel="alternate" href="https://ccw.dev/docs/404.html" hreflang="x-default"><link rel="stylesheet" href="/docs/assets/css/styles.43777f0a.css">
|
||||
<script src="/docs/assets/js/runtime~main.cbfbf29d.js" defer="defer"></script>
|
||||
<script src="/docs/assets/js/main.6c671094.js" defer="defer"></script>
|
||||
<title data-rh="true">Page Not Found | CCW Help Documentation</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:url" content="http://localhost:3001/docs/404.html"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" property="og:locale:alternate" content="zh"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docusaurus_tag" content="default"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docsearch:docusaurus_tag" content="default"><meta data-rh="true" property="og:title" content="Page Not Found | CCW Help Documentation"><link data-rh="true" rel="icon" href="/docs/img/favicon.ico"><link data-rh="true" rel="canonical" href="http://localhost:3001/docs/404.html"><link data-rh="true" rel="alternate" href="http://localhost:3001/docs/404.html" hreflang="en"><link data-rh="true" rel="alternate" href="http://localhost:3001/docs/zh/404.html" hreflang="zh"><link data-rh="true" rel="alternate" href="http://localhost:3001/docs/404.html" hreflang="x-default"><link rel="stylesheet" href="/docs/assets/css/styles.43777f0a.css">
|
||||
<script src="/docs/assets/js/runtime~main.0220a52f.js" defer="defer"></script>
|
||||
<script src="/docs/assets/js/main.d2194b90.js" defer="defer"></script>
|
||||
</head>
|
||||
<body class="navigation-with-keyboard">
|
||||
<svg style="display: none;"><defs>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
ccw/docs-site/build/assets/js/18891827.f868bfb7.js
Normal file
1
ccw/docs-site/build/assets/js/18891827.f868bfb7.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
"use strict";(globalThis.webpackChunkccw_docs=globalThis.webpackChunkccw_docs||[]).push([[647],{3531(c,s,e){e.r(s),e.d(s,{default:()=>h});e(4041);var r=e(4357),a=e(7473),u=e(8582),l=e(8150),o=e(6270),d=e(1085);function h(c){return(0,d.jsx)(u.e3,{className:(0,r.A)(a.G.wrapper.docsPages),children:(0,d.jsx)(o.A,{children:(0,l.v)(c.route.routes)})})}}}]);
|
||||
"use strict";(globalThis.webpackChunkccw_docs=globalThis.webpackChunkccw_docs||[]).push([[647],{3531(c,s,e){e.r(s),e.d(s,{default:()=>h});e(3696);var r=e(4357),a=e(7473),u=e(8582),l=e(8150),o=e(6270),d=e(2540);function h(c){return(0,d.jsx)(u.e3,{className:(0,r.A)(a.G.wrapper.docsPages),children:(0,d.jsx)(o.A,{children:(0,l.v)(c.route.routes)})})}}}]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
"use strict";(globalThis.webpackChunkccw_docs=globalThis.webpackChunkccw_docs||[]).push([[723],{2757(e,t,i){i.d(t,{A:()=>r});i(4041);var n=i(4357),o=i(9082),s=i(4441),a=i(1085);function r({className:e}){return(0,a.jsx)("main",{className:(0,n.A)("container margin-vert--xl",e),children:(0,a.jsx)("div",{className:"row",children:(0,a.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,a.jsx)(s.A,{as:"h1",className:"hero__title",children:(0,a.jsx)(o.A,{id:"theme.NotFound.title",description:"The title of the 404 page",children:"Page Not Found"})}),(0,a.jsx)("p",{children:(0,a.jsx)(o.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page",children:"We could not find what you were looking for."})}),(0,a.jsx)("p",{children:(0,a.jsx)(o.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page",children:"Please contact the owner of the site that linked you to the original URL and let them know their link is broken."})})]})})})}},5723(e,t,i){i.r(t),i.d(t,{default:()=>c});i(4041);var n=i(9082),o=i(8582),s=i(6270),a=i(2757),r=i(1085);function c(){const e=(0,n.T)({id:"theme.NotFound.title",message:"Page Not Found"});return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(o.be,{title:e}),(0,r.jsx)(s.A,{children:(0,r.jsx)(a.A,{})})]})}}}]);
|
||||
"use strict";(globalThis.webpackChunkccw_docs=globalThis.webpackChunkccw_docs||[]).push([[723],{2757(e,t,i){i.d(t,{A:()=>r});i(3696);var n=i(4357),o=i(9082),s=i(4441),a=i(2540);function r({className:e}){return(0,a.jsx)("main",{className:(0,n.A)("container margin-vert--xl",e),children:(0,a.jsx)("div",{className:"row",children:(0,a.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,a.jsx)(s.A,{as:"h1",className:"hero__title",children:(0,a.jsx)(o.A,{id:"theme.NotFound.title",description:"The title of the 404 page",children:"Page Not Found"})}),(0,a.jsx)("p",{children:(0,a.jsx)(o.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page",children:"We could not find what you were looking for."})}),(0,a.jsx)("p",{children:(0,a.jsx)(o.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page",children:"Please contact the owner of the site that linked you to the original URL and let them know their link is broken."})})]})})})}},5723(e,t,i){i.r(t),i.d(t,{default:()=>c});i(3696);var n=i(9082),o=i(8582),s=i(6270),a=i(2757),r=i(2540);function c(){const e=(0,n.T)({id:"theme.NotFound.title",message:"Page Not Found"});return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(o.be,{title:e}),(0,r.jsx)(s.A,{children:(0,r.jsx)(a.A,{})})]})}}}]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
ccw/docs-site/build/assets/js/a2065270.5d0fec0e.js
Normal file
1
ccw/docs-site/build/assets/js/a2065270.5d0fec0e.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
"use strict";(globalThis.webpackChunkccw_docs=globalThis.webpackChunkccw_docs||[]).push([[98],{3045(n,s,e){e.r(s),e.d(s,{default:()=>d});e(4041);var r=e(8582);function o(n,s){return`docs-${n}-${s}`}var c=e(4647),t=e(8150),i=e(6613),a=e(1085);function u(n){const{version:s}=n;return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(i.A,{version:s.version,tag:o(s.pluginId,s.version)}),(0,a.jsx)(r.be,{children:s.noIndex&&(0,a.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})]})}function l(n){const{version:s,route:e}=n;return(0,a.jsx)(r.e3,{className:s.className,children:(0,a.jsx)(c.n,{version:s,children:(0,t.v)(e.routes)})})}function d(n){return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(u,{...n}),(0,a.jsx)(l,{...n})]})}}}]);
|
||||
"use strict";(globalThis.webpackChunkccw_docs=globalThis.webpackChunkccw_docs||[]).push([[98],{3045(n,s,e){e.r(s),e.d(s,{default:()=>d});e(3696);var r=e(8582);function o(n,s){return`docs-${n}-${s}`}var c=e(4647),t=e(8150),i=e(6613),a=e(2540);function u(n){const{version:s}=n;return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(i.A,{version:s.version,tag:o(s.pluginId,s.version)}),(0,a.jsx)(r.be,{children:s.noIndex&&(0,a.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})]})}function l(n){const{version:s,route:e}=n;return(0,a.jsx)(r.e3,{className:s.className,children:(0,a.jsx)(c.n,{version:s,children:(0,t.v)(e.routes)})})}function d(n){return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(u,{...n}),(0,a.jsx)(l,{...n})]})}}}]);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
ccw/docs-site/build/assets/js/f4817052.e0e6bfe1.js
Normal file
1
ccw/docs-site/build/assets/js/f4817052.e0e6bfe1.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
ccw/docs-site/build/assets/js/main.d2194b90.js
Normal file
2
ccw/docs-site/build/assets/js/main.d2194b90.js
Normal file
File diff suppressed because one or more lines are too long
1
ccw/docs-site/build/assets/js/runtime~main.0220a52f.js
Normal file
1
ccw/docs-site/build/assets/js/runtime~main.0220a52f.js
Normal file
@@ -0,0 +1 @@
|
||||
(()=>{"use strict";var e,a,c,r,t,f={},o={};function d(e){var a=o[e];if(void 0!==a)return a.exports;var c=o[e]={exports:{}};return f[e].call(c.exports,c,c.exports,d),c.exports}d.m=f,e=[],d.O=(a,c,r,t)=>{if(!c){var f=1/0;for(i=0;i<e.length;i++){for(var[c,r,t]=e[i],o=!0,b=0;b<c.length;b++)(!1&t||f>=t)&&Object.keys(d.O).every(e=>d.O[e](c[b]))?c.splice(b--,1):(o=!1,t<f&&(f=t));if(o){e.splice(i--,1);var n=r();void 0!==n&&(a=n)}}return a}t=t||0;for(var i=e.length;i>0&&e[i-1][2]>t;i--)e[i]=e[i-1];e[i]=[c,r,t]},d.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return d.d(a,{a:a}),a},c=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,d.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var t=Object.create(null);d.r(t);var f={};a=a||[null,c({}),c([]),c(c)];for(var o=2&r&&e;("object"==typeof o||"function"==typeof o)&&!~a.indexOf(o);o=c(o))Object.getOwnPropertyNames(o).forEach(a=>f[a]=()=>e[a]);return f.default=()=>e,d.d(t,f),t},d.d=(e,a)=>{for(var c in a)d.o(a,c)&&!d.o(e,c)&&Object.defineProperty(e,c,{enumerable:!0,get:a[c]})},d.f={},d.e=e=>Promise.all(Object.keys(d.f).reduce((a,c)=>(d.f[c](e,a),a),[])),d.u=e=>"assets/js/"+({11:"ea313555",17:"ccef5d0f",47:"157db180",48:"a94703ab",57:"19b64556",98:"a7bd4aaa",121:"5c7b2278",142:"0566a0a8",148:"4cc74730",235:"18891827",241:"d045285b",268:"60eef997",288:"666bb1bf",368:"186dcf4e",401:"17896441",407:"611877e1",411:"d550a629",412:"1bac9067",448:"f9222419",482:"c5a82d8d",511:"bdb2b105",647:"5e95c892",725:"bcf6b37c",742:"aba21aa0",777:"fabaf1c8",814:"97c6e66a",816:"a2065270",849:"4ad7db0f",856:"2ecf8b4a",896:"f4817052",927:"04db0a2e",934:"7a1ee27c",954:"f1bf82ec",971:"fe8e3dcf",973:"9f4ca91e",975:"1e3006f3",991:"a6c3df16"}[e]||e)+"."+{11:"5ab441e2",17:"265182f6",47:"600b1451",48:"7b43e8e3",57:"6a97ef6e",98:"e4fb75f9",121:"20942c2c",142:"9fc6236d",148:"d5719b6e",235:"f868bfb7",241:"7af76fbc",268:"c3e43e3b",288:"3f6acfd8",368:"9cc2830f",401:"d1575d23",407:"94144df0",411:"236e0027",412:"44948c57",448:"9eaf88b7",482:"a992435d",511:"836bfb71",647:"9f686774",723:"50d99bed",725:"b5f365c1",742:"dc3eeab8",777:"4dc921e5",814:"c0137c74",816:"5d0fec0e",849:"fdce606b",856:"b5d4721e",896:"e0e6bfe1",927:"0694cac9",934:"7f87648d",954:"1157ef45",971:"548de575",973:"8efe3ed3",975:"9dddfb7c",991:"7cfa0c0a"}[e]+".js",d.miniCssF=e=>{},d.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),r={},t="ccw-docs:",d.l=(e,a,c,f)=>{if(r[e])r[e].push(a);else{var o,b;if(void 0!==c)for(var n=document.getElementsByTagName("script"),i=0;i<n.length;i++){var l=n[i];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==t+c){o=l;break}}o||(b=!0,(o=document.createElement("script")).charset="utf-8",d.nc&&o.setAttribute("nonce",d.nc),o.setAttribute("data-webpack",t+c),o.src=e),r[e]=[a];var u=(a,c)=>{o.onerror=o.onload=null,clearTimeout(s);var t=r[e];if(delete r[e],o.parentNode&&o.parentNode.removeChild(o),t&&t.forEach(e=>e(c)),a)return a(c)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=u.bind(null,o.onerror),o.onload=u.bind(null,o.onload),b&&document.head.appendChild(o)}},d.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.p="/docs/",d.gca=function(e){return e={17896441:"401",18891827:"235",ea313555:"11",ccef5d0f:"17","157db180":"47",a94703ab:"48","19b64556":"57",a7bd4aaa:"98","5c7b2278":"121","0566a0a8":"142","4cc74730":"148",d045285b:"241","60eef997":"268","666bb1bf":"288","186dcf4e":"368","611877e1":"407",d550a629:"411","1bac9067":"412",f9222419:"448",c5a82d8d:"482",bdb2b105:"511","5e95c892":"647",bcf6b37c:"725",aba21aa0:"742",fabaf1c8:"777","97c6e66a":"814",a2065270:"816","4ad7db0f":"849","2ecf8b4a":"856",f4817052:"896","04db0a2e":"927","7a1ee27c":"934",f1bf82ec:"954",fe8e3dcf:"971","9f4ca91e":"973","1e3006f3":"975",a6c3df16:"991"}[e]||e,d.p+d.u(e)},(()=>{var e={354:0,869:0};d.f.j=(a,c)=>{var r=d.o(e,a)?e[a]:void 0;if(0!==r)if(r)c.push(r[2]);else if(/^(354|869)$/.test(a))e[a]=0;else{var t=new Promise((c,t)=>r=e[a]=[c,t]);c.push(r[2]=t);var f=d.p+d.u(a),o=new Error;d.l(f,c=>{if(d.o(e,a)&&(0!==(r=e[a])&&(e[a]=void 0),r)){var t=c&&("load"===c.type?"missing":c.type),f=c&&c.target&&c.target.src;o.message="Loading chunk "+a+" failed.\n("+t+": "+f+")",o.name="ChunkLoadError",o.type=t,o.request=f,r[1](o)}},"chunk-"+a,a)}},d.O.j=a=>0===e[a];var a=(a,c)=>{var r,t,[f,o,b]=c,n=0;if(f.some(a=>0!==e[a])){for(r in o)d.o(o,r)&&(d.m[r]=o[r]);if(b)var i=b(d)}for(a&&a(c);n<f.length;n++)t=f[n],d.o(e,t)&&e[t]&&e[t][0](),e[t]=0;return d.O(i)},c=globalThis.webpackChunkccw_docs=globalThis.webpackChunkccw_docs||[];c.forEach(a.bind(null,0)),c.push=a.bind(null,c.push.bind(c))})()})();
|
||||
@@ -1 +0,0 @@
|
||||
(()=>{"use strict";var e,a,r,t,f,c={},o={};function b(e){var a=o[e];if(void 0!==a)return a.exports;var r=o[e]={exports:{}};return c[e].call(r.exports,r,r.exports,b),r.exports}b.m=c,e=[],b.O=(a,r,t,f)=>{if(!r){var c=1/0;for(i=0;i<e.length;i++){for(var[r,t,f]=e[i],o=!0,d=0;d<r.length;d++)(!1&f||c>=f)&&Object.keys(b.O).every(e=>b.O[e](r[d]))?r.splice(d--,1):(o=!1,f<c&&(c=f));if(o){e.splice(i--,1);var n=t();void 0!==n&&(a=n)}}return a}f=f||0;for(var i=e.length;i>0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[r,t,f]},b.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return b.d(a,{a:a}),a},r=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,b.t=function(e,t){if(1&t&&(e=this(e)),8&t)return e;if("object"==typeof e&&e){if(4&t&&e.__esModule)return e;if(16&t&&"function"==typeof e.then)return e}var f=Object.create(null);b.r(f);var c={};a=a||[null,r({}),r([]),r(r)];for(var o=2&t&&e;("object"==typeof o||"function"==typeof o)&&!~a.indexOf(o);o=r(o))Object.getOwnPropertyNames(o).forEach(a=>c[a]=()=>e[a]);return c.default=()=>e,b.d(f,c),f},b.d=(e,a)=>{for(var r in a)b.o(a,r)&&!b.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:a[r]})},b.f={},b.e=e=>Promise.all(Object.keys(b.f).reduce((a,r)=>(b.f[r](e,a),a),[])),b.u=e=>"assets/js/"+({11:"ea313555",17:"ccef5d0f",47:"157db180",48:"a94703ab",57:"19b64556",98:"a7bd4aaa",121:"5c7b2278",142:"0566a0a8",148:"4cc74730",235:"18891827",241:"d045285b",268:"60eef997",288:"666bb1bf",368:"186dcf4e",401:"17896441",407:"611877e1",411:"d550a629",412:"1bac9067",448:"f9222419",482:"c5a82d8d",511:"bdb2b105",647:"5e95c892",725:"bcf6b37c",742:"aba21aa0",777:"fabaf1c8",814:"97c6e66a",816:"a2065270",849:"4ad7db0f",856:"2ecf8b4a",896:"f4817052",927:"04db0a2e",934:"7a1ee27c",954:"f1bf82ec",971:"fe8e3dcf",973:"9f4ca91e",975:"1e3006f3",991:"a6c3df16"}[e]||e)+"."+{11:"4eea9e04",17:"421f57e1",47:"4fb84679",48:"5b347b84",57:"a6163a0a",98:"5f0b376e",121:"0eaf76aa",142:"13f3324d",148:"4af80d66",235:"d20470dc",241:"73fb2bfa",268:"a1401c2f",288:"2da41127",368:"dc0319e0",401:"a28ce98e",407:"71e7b278",411:"41f98adf",412:"2e1f90de",448:"eca02b22",482:"c941bbe4",511:"00be57de",647:"1a07266b",723:"b1cb938e",725:"19ccccf7",742:"dc3eeab8",777:"811090ab",814:"baa03fb6",816:"088080b0",849:"62c9b350",856:"9d518d70",896:"a5126a55",927:"2c353bc4",934:"67535332",954:"e2a6e8e1",971:"0a78caef",973:"ea7dd9f3",975:"6141b6ef",991:"fddd53e1"}[e]+".js",b.miniCssF=e=>{},b.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),t={},f="ccw-docs:",b.l=(e,a,r,c)=>{if(t[e])t[e].push(a);else{var o,d;if(void 0!==r)for(var n=document.getElementsByTagName("script"),i=0;i<n.length;i++){var l=n[i];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==f+r){o=l;break}}o||(d=!0,(o=document.createElement("script")).charset="utf-8",b.nc&&o.setAttribute("nonce",b.nc),o.setAttribute("data-webpack",f+r),o.src=e),t[e]=[a];var u=(a,r)=>{o.onerror=o.onload=null,clearTimeout(s);var f=t[e];if(delete t[e],o.parentNode&&o.parentNode.removeChild(o),f&&f.forEach(e=>e(r)),a)return a(r)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:o}),12e4);o.onerror=u.bind(null,o.onerror),o.onload=u.bind(null,o.onload),d&&document.head.appendChild(o)}},b.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},b.p="/docs/",b.gca=function(e){return e={17896441:"401",18891827:"235",ea313555:"11",ccef5d0f:"17","157db180":"47",a94703ab:"48","19b64556":"57",a7bd4aaa:"98","5c7b2278":"121","0566a0a8":"142","4cc74730":"148",d045285b:"241","60eef997":"268","666bb1bf":"288","186dcf4e":"368","611877e1":"407",d550a629:"411","1bac9067":"412",f9222419:"448",c5a82d8d:"482",bdb2b105:"511","5e95c892":"647",bcf6b37c:"725",aba21aa0:"742",fabaf1c8:"777","97c6e66a":"814",a2065270:"816","4ad7db0f":"849","2ecf8b4a":"856",f4817052:"896","04db0a2e":"927","7a1ee27c":"934",f1bf82ec:"954",fe8e3dcf:"971","9f4ca91e":"973","1e3006f3":"975",a6c3df16:"991"}[e]||e,b.p+b.u(e)},(()=>{var e={354:0,869:0};b.f.j=(a,r)=>{var t=b.o(e,a)?e[a]:void 0;if(0!==t)if(t)r.push(t[2]);else if(/^(354|869)$/.test(a))e[a]=0;else{var f=new Promise((r,f)=>t=e[a]=[r,f]);r.push(t[2]=f);var c=b.p+b.u(a),o=new Error;b.l(c,r=>{if(b.o(e,a)&&(0!==(t=e[a])&&(e[a]=void 0),t)){var f=r&&("load"===r.type?"missing":r.type),c=r&&r.target&&r.target.src;o.message="Loading chunk "+a+" failed.\n("+f+": "+c+")",o.name="ChunkLoadError",o.type=f,o.request=c,t[1](o)}},"chunk-"+a,a)}},b.O.j=a=>0===e[a];var a=(a,r)=>{var t,f,[c,o,d]=r,n=0;if(c.some(a=>0!==e[a])){for(t in o)b.o(o,t)&&(b.m[t]=o[t]);if(d)var i=d(b)}for(a&&a(r);n<c.length;n++)f=c[n],b.o(e,f)&&e[f]&&e[f][0](),e[f]=0;return b.O(i)},r=globalThis.webpackChunkccw_docs=globalThis.webpackChunkccw_docs||[];r.forEach(a.bind(null,0)),r.push=a.bind(null,r.push.bind(r))})()})();
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://ccw.dev/docs/docs/</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/cli/cli-init</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/cli/codex-review</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/general/ccw</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/general/ccw-coordinator</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/general/ccw-debug</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/general/ccw-plan</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/general/ccw-test</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/general/codex-coordinator</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/general/flow-create</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/issue/issue-convert-to-plan</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/issue/issue-discover</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/issue/issue-execute</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/issue/issue-from-brainstorm</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/issue/issue-new</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/issue/issue-plan</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/issue/issue-queue</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/memory/memory-compact</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/memory/memory-docs-full-cli</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/memory/memory-docs-related-cli</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/memory/memory-load</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/memory/memory-update-full</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/commands/memory/memory-update-related</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/faq</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/workflows/faq</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/workflows/introduction</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/workflows/level-1-ultra-lightweight</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/workflows/level-2-rapid</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/workflows/level-3-standard</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/workflows/level-4-brainstorm</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>https://ccw.dev/docs/docs/workflows/level-5-intelligent</loc><changefreq>weekly</changefreq><priority>0.5</priority></url></urlset>
|
||||
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>http://localhost:3001/docs/docs/commands/cli/cli-init</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/cli/codex-review</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/general/ccw</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/general/ccw-coordinator</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/general/ccw-debug</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/general/ccw-plan</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/general/ccw-test</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/general/codex-coordinator</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/general/flow-create</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/issue/issue-convert-to-plan</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/issue/issue-discover</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/issue/issue-execute</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/issue/issue-from-brainstorm</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/issue/issue-new</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/issue/issue-plan</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/issue/issue-queue</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/memory/memory-compact</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/memory/memory-docs-full-cli</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/memory/memory-docs-related-cli</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/memory/memory-load</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/memory/memory-update-full</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/commands/memory/memory-update-related</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/faq</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/overview</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/workflows/faq</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/workflows/introduction</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/workflows/level-1-ultra-lightweight</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/workflows/level-2-rapid</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/workflows/level-3-standard</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/workflows/level-4-brainstorm</loc><changefreq>weekly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3001/docs/docs/workflows/level-5-intelligent</loc><changefreq>weekly</changefreq><priority>0.5</priority></url></urlset>
|
||||
@@ -39,6 +39,7 @@
|
||||
"allotment": "^1.20.5",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"lucide-react": "^0.460.0",
|
||||
"react": "^18.3.1",
|
||||
|
||||
@@ -69,8 +69,9 @@ export function QueueActions({
|
||||
const [mergeTargetId, setMergeTargetId] = useState('');
|
||||
const [selectedItemIds, setSelectedItemIds] = useState<string[]>([]);
|
||||
|
||||
// Get queue ID - IssueQueue interface doesn't have an id field, using tasks array as key
|
||||
const queueId = (queue.tasks?.join(',') || queue.solutions?.join(',') || 'default');
|
||||
// Use "current" as the queue ID for single-queue model
|
||||
// This matches the API pattern where deactivate works on the current queue
|
||||
const queueId = 'current';
|
||||
|
||||
// Get all items from grouped_items for split dialog
|
||||
const allItems: QueueItem[] = Object.values(queue.grouped_items || {}).flat();
|
||||
|
||||
@@ -51,8 +51,8 @@ export function QueueCard({
|
||||
}: QueueCardProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
// Get queue ID - IssueQueue interface doesn't have an id field, using tasks array as key
|
||||
const queueId = (queue.tasks || []).join(',') || (queue.solutions || []).join(',') || 'unknown';
|
||||
// Use "current" for queue ID display
|
||||
const queueId = 'current';
|
||||
|
||||
// Calculate item counts
|
||||
const taskCount = queue.tasks?.length || 0;
|
||||
|
||||
@@ -14,7 +14,7 @@ import { NotificationPanel } from '@/components/notification';
|
||||
import { AskQuestionDialog } from '@/components/a2ui/AskQuestionDialog';
|
||||
import { useNotificationStore, selectCurrentQuestion } from '@/stores';
|
||||
import { useWorkflowStore } from '@/stores/workflowStore';
|
||||
import { useWebSocketNotifications } from '@/hooks';
|
||||
import { useWebSocketNotifications, useWebSocket } from '@/hooks';
|
||||
|
||||
export interface AppShellProps {
|
||||
/** Initial sidebar collapsed state */
|
||||
@@ -103,7 +103,8 @@ export function AppShell({
|
||||
const currentQuestion = useNotificationStore(selectCurrentQuestion);
|
||||
const setCurrentQuestion = useNotificationStore((state) => state.setCurrentQuestion);
|
||||
|
||||
// Initialize WebSocket notifications handler
|
||||
// Initialize WebSocket connection and notifications handler
|
||||
useWebSocket();
|
||||
useWebSocketNotifications();
|
||||
|
||||
// Load persistent notifications from localStorage on mount
|
||||
|
||||
@@ -117,6 +117,7 @@ const navGroupDefinitions: NavGroupDef[] = [
|
||||
icon: Cog,
|
||||
items: [
|
||||
{ path: '/settings', labelKey: 'navigation.main.settings', icon: Settings },
|
||||
{ path: '/settings/mcp', labelKey: 'navigation.main.mcp', icon: Server },
|
||||
{ path: '/settings/rules', labelKey: 'navigation.main.rules', icon: Shield },
|
||||
{ path: '/settings/codexlens', labelKey: 'navigation.main.codexlens', icon: Sparkles },
|
||||
{ path: '/api-settings', labelKey: 'navigation.main.apiSettings', icon: Server },
|
||||
|
||||
267
ccw/frontend/src/components/mcp/AllProjectsTable.tsx
Normal file
267
ccw/frontend/src/components/mcp/AllProjectsTable.tsx
Normal file
@@ -0,0 +1,267 @@
|
||||
// ========================================
|
||||
// All Projects Table Component
|
||||
// ========================================
|
||||
// Table component displaying all recent projects with MCP server statistics
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Folder, Clock, Database, ExternalLink } from 'lucide-react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { useProjectOperations } from '@/hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
export interface ProjectServerStats {
|
||||
name: string;
|
||||
path: string;
|
||||
serverCount: number;
|
||||
enabledCount: number;
|
||||
lastModified?: string;
|
||||
isCurrent?: boolean;
|
||||
}
|
||||
|
||||
export interface AllProjectsTableProps {
|
||||
/** Callback when a project is clicked */
|
||||
onProjectClick?: (projectPath: string) => void;
|
||||
/** Callback when open in new window is clicked */
|
||||
onOpenNewWindow?: (projectPath: string) => void;
|
||||
/** Additional class name */
|
||||
className?: string;
|
||||
/** Maximum number of projects to display */
|
||||
maxProjects?: number;
|
||||
}
|
||||
|
||||
// ========== Component ==========
|
||||
|
||||
export function AllProjectsTable({
|
||||
onProjectClick,
|
||||
onOpenNewWindow,
|
||||
className,
|
||||
maxProjects,
|
||||
}: AllProjectsTableProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const [sortField, setSortField] = useState<'name' | 'serverCount' | 'lastModified'>('lastModified');
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
|
||||
|
||||
const { projects, currentProject, isLoading } = useProjectOperations();
|
||||
|
||||
// Mock server counts since backend doesn't provide per-project stats
|
||||
// In production, this would come from a dedicated API endpoint
|
||||
const projectStats: ProjectServerStats[] = projects.slice(0, maxProjects).map((path) => {
|
||||
const isCurrent = path === currentProject;
|
||||
// Extract name from path (last segment)
|
||||
const name = path.split(/[/\\]/).filter(Boolean).pop() || path;
|
||||
|
||||
return {
|
||||
name,
|
||||
path,
|
||||
serverCount: Math.floor(Math.random() * 10), // Mock data
|
||||
enabledCount: Math.floor(Math.random() * 8), // Mock data
|
||||
lastModified: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
isCurrent,
|
||||
};
|
||||
});
|
||||
|
||||
// Sort projects
|
||||
const sortedProjects = [...projectStats].sort((a, b) => {
|
||||
let comparison = 0;
|
||||
|
||||
switch (sortField) {
|
||||
case 'name':
|
||||
comparison = a.name.localeCompare(b.name);
|
||||
break;
|
||||
case 'serverCount':
|
||||
comparison = a.serverCount - b.serverCount;
|
||||
break;
|
||||
case 'lastModified':
|
||||
comparison = a.lastModified && b.lastModified
|
||||
? new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime()
|
||||
: 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return sortDirection === 'asc' ? comparison : -comparison;
|
||||
});
|
||||
|
||||
// Handle sort
|
||||
const handleSort = (field: typeof sortField) => {
|
||||
if (sortField === field) {
|
||||
setSortDirection((prev) => (prev === 'asc' ? 'desc' : 'asc'));
|
||||
} else {
|
||||
setSortField(field);
|
||||
setSortDirection('desc');
|
||||
}
|
||||
};
|
||||
|
||||
// Handle project click
|
||||
const handleProjectClick = (projectPath: string) => {
|
||||
onProjectClick?.(projectPath);
|
||||
};
|
||||
|
||||
// Handle open in new window
|
||||
const handleOpenNewWindow = (e: React.MouseEvent, projectPath: string) => {
|
||||
e.stopPropagation();
|
||||
onOpenNewWindow?.(projectPath);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Card className={cn('p-8', className)}>
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="animate-spin text-muted-foreground">-</div>
|
||||
<span className="ml-2 text-sm text-muted-foreground">
|
||||
{formatMessage({ id: 'common.actions.loading' })}
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (sortedProjects.length === 0) {
|
||||
return (
|
||||
<Card className={cn('p-8', className)}>
|
||||
<div className="text-center">
|
||||
<Folder className="w-12 h-12 mx-auto text-muted-foreground/50 mb-3" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{formatMessage({ id: 'mcp.allProjects.empty' })}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className={cn('overflow-hidden', className)}>
|
||||
{/* Table Header */}
|
||||
<div className="grid grid-cols-12 gap-2 px-4 py-3 bg-muted/50 border-b border-border text-xs font-medium text-muted-foreground uppercase">
|
||||
<div className="col-span-4">
|
||||
<button
|
||||
onClick={() => handleSort('name')}
|
||||
className="flex items-center gap-1 hover:text-foreground transition-colors"
|
||||
>
|
||||
{formatMessage({ id: 'mcp.allProjects.name' })}
|
||||
{sortField === 'name' && (
|
||||
<span className="text-foreground">{sortDirection === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<button
|
||||
onClick={() => handleSort('serverCount')}
|
||||
className="flex items-center gap-1 hover:text-foreground transition-colors"
|
||||
>
|
||||
{formatMessage({ id: 'mcp.allProjects.servers' })}
|
||||
{sortField === 'serverCount' && (
|
||||
<span className="text-foreground">{sortDirection === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-span-3">
|
||||
<button
|
||||
onClick={() => handleSort('lastModified')}
|
||||
className="flex items-center gap-1 hover:text-foreground transition-colors"
|
||||
>
|
||||
{formatMessage({ id: 'mcp.allProjects.lastModified' })}
|
||||
{sortField === 'lastModified' && (
|
||||
<span className="text-foreground">{sortDirection === 'asc' ? '↑' : '↓'}</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-span-2 text-right">
|
||||
{formatMessage({ id: 'mcp.allProjects.actions' })}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table Rows */}
|
||||
<div className="divide-y divide-border">
|
||||
{sortedProjects.map((project) => (
|
||||
<div
|
||||
key={project.path}
|
||||
onClick={() => handleProjectClick(project.path)}
|
||||
className={cn(
|
||||
'grid grid-cols-12 gap-2 px-4 py-3 items-center transition-colors cursor-pointer hover:bg-muted/50',
|
||||
project.isCurrent && 'bg-primary/5 hover:bg-primary/10'
|
||||
)}
|
||||
>
|
||||
{/* Project Name */}
|
||||
<div className="col-span-4 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<Folder className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-foreground truncate">
|
||||
{project.name}
|
||||
</span>
|
||||
{project.isCurrent && (
|
||||
<Badge variant="default" className="text-xs">
|
||||
{formatMessage({ id: 'mcp.allProjects.current' })}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-mono truncate">
|
||||
{project.path}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Server Count */}
|
||||
<div className="col-span-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Database className="w-4 h-4 text-muted-foreground" />
|
||||
<span className="text-sm text-foreground">
|
||||
{project.serverCount} {formatMessage({ id: 'mcp.allProjects.servers' })}
|
||||
</span>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={cn(
|
||||
'text-xs',
|
||||
project.enabledCount > 0 ? 'text-success border-success/30' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{project.enabledCount} {formatMessage({ id: 'mcp.status.enabled' })}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Last Modified */}
|
||||
<div className="col-span-3">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>
|
||||
{project.lastModified
|
||||
? formatDistanceToNow(new Date(project.lastModified), { addSuffix: true })
|
||||
: '-'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="col-span-2 flex justify-end gap-1">
|
||||
<button
|
||||
onClick={(e) => handleOpenNewWindow(e, project.path)}
|
||||
className="p-1.5 rounded hover:bg-muted transition-colors"
|
||||
title={formatMessage({ id: 'mcp.allProjects.openNewWindow' })}
|
||||
>
|
||||
<ExternalLink className="w-4 h-4 text-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="px-4 py-2 bg-muted/30 border-t border-border text-xs text-muted-foreground">
|
||||
{formatMessage(
|
||||
{ id: 'mcp.allProjects.summary' },
|
||||
{ count: sortedProjects.length }
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default AllProjectsTable;
|
||||
@@ -24,6 +24,8 @@ export interface CodexMcpCardProps {
|
||||
enabled: boolean;
|
||||
isExpanded: boolean;
|
||||
onToggleExpand: () => void;
|
||||
/** Optional: When true, indicates this card is in editable mode (for CodexMcpEditableCard extension) */
|
||||
isEditable?: boolean;
|
||||
}
|
||||
|
||||
// ========== Component ==========
|
||||
@@ -33,6 +35,8 @@ export function CodexMcpCard({
|
||||
enabled,
|
||||
isExpanded,
|
||||
onToggleExpand,
|
||||
// isEditable prop is for CodexMcpEditableCard extension compatibility
|
||||
isEditable: _isEditable = false,
|
||||
}: CodexMcpCardProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
|
||||
306
ccw/frontend/src/components/mcp/CodexMcpEditableCard.tsx
Normal file
306
ccw/frontend/src/components/mcp/CodexMcpEditableCard.tsx
Normal file
@@ -0,0 +1,306 @@
|
||||
// ========================================
|
||||
// Codex MCP Editable Card Component
|
||||
// ========================================
|
||||
// Editable Codex MCP server card with remove and toggle actions
|
||||
// Extends CodexMcpCard with additional action buttons when editing is enabled
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
Server,
|
||||
Power,
|
||||
PowerOff,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Edit3,
|
||||
Trash2,
|
||||
Lock,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from '@/components/ui/AlertDialog';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { McpServer } from '@/lib/api';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
export interface CodexMcpEditableCardProps {
|
||||
server: McpServer;
|
||||
enabled: boolean;
|
||||
isExpanded: boolean;
|
||||
onToggleExpand: () => void;
|
||||
/** When enabled, shows remove/toggle buttons instead of read-only badge */
|
||||
isEditable?: boolean;
|
||||
/** Callback when server is removed (after confirmation) */
|
||||
onRemove?: (serverName: string) => void | Promise<void>;
|
||||
/** Callback when server is toggled */
|
||||
onToggle?: (serverName: string, enabled: boolean) => void | Promise<void>;
|
||||
/** Whether remove operation is in progress */
|
||||
isRemoving?: boolean;
|
||||
/** Whether toggle operation is in progress */
|
||||
isToggling?: boolean;
|
||||
}
|
||||
|
||||
// ========== Component ==========
|
||||
|
||||
export function CodexMcpEditableCard({
|
||||
server,
|
||||
enabled,
|
||||
isExpanded,
|
||||
onToggleExpand,
|
||||
isEditable = false,
|
||||
onRemove,
|
||||
onToggle,
|
||||
isRemoving = false,
|
||||
isToggling = false,
|
||||
}: CodexMcpEditableCardProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const [isConfirmDeleteOpen, setIsConfirmDeleteOpen] = useState(false);
|
||||
|
||||
// Handle toggle with optimistic update
|
||||
const handleToggle = async () => {
|
||||
if (onToggle) {
|
||||
await onToggle(server.name, !enabled);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle remove with confirmation
|
||||
const handleRemove = async () => {
|
||||
if (onRemove) {
|
||||
await onRemove(server.name);
|
||||
setIsConfirmDeleteOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Prevent click propagation when clicking action buttons
|
||||
const stopPropagation = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={cn('overflow-hidden', !enabled && 'opacity-60')}>
|
||||
{/* Header */}
|
||||
<div
|
||||
className="p-4 cursor-pointer hover:bg-muted/50 transition-colors"
|
||||
onClick={onToggleExpand}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={cn(
|
||||
'p-2 rounded-lg',
|
||||
enabled ? 'bg-primary/10' : 'bg-muted'
|
||||
)}>
|
||||
<Server className={cn(
|
||||
'w-5 h-5',
|
||||
enabled ? 'text-primary' : 'text-muted-foreground'
|
||||
)} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{server.name}
|
||||
</span>
|
||||
{isEditable ? (
|
||||
<>
|
||||
{/* Editable badge with actions */}
|
||||
<Badge variant="secondary" className="text-xs flex items-center gap-1">
|
||||
<Edit3 className="w-3 h-3" />
|
||||
{formatMessage({ id: 'mcp.codex.editable' })}
|
||||
</Badge>
|
||||
{enabled && (
|
||||
<Badge variant="outline" className="text-xs text-green-600">
|
||||
<Power className="w-3 h-3 mr-1" />
|
||||
{formatMessage({ id: 'mcp.status.enabled' })}
|
||||
</Badge>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* Read-only badge */}
|
||||
<Badge variant="secondary" className="text-xs flex items-center gap-1">
|
||||
<Lock className="w-3 h-3" />
|
||||
{formatMessage({ id: 'mcp.codex.readOnly' })}
|
||||
</Badge>
|
||||
{enabled && (
|
||||
<Badge variant="outline" className="text-xs text-green-600">
|
||||
<Power className="w-3 h-3 mr-1" />
|
||||
{formatMessage({ id: 'mcp.status.enabled' })}
|
||||
</Badge>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1 font-mono">
|
||||
{server.command} {server.args?.join(' ') || ''}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{isEditable ? (
|
||||
<>
|
||||
{/* Toggle button (active in editable mode) */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={(e) => {
|
||||
stopPropagation(e);
|
||||
handleToggle();
|
||||
}}
|
||||
disabled={isToggling}
|
||||
>
|
||||
{enabled ? (
|
||||
<Power className="w-4 h-4 text-green-600" />
|
||||
) : (
|
||||
<PowerOff className="w-4 h-4 text-muted-foreground" />
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{/* Remove button with confirmation */}
|
||||
<AlertDialog open={isConfirmDeleteOpen} onOpenChange={setIsConfirmDeleteOpen}>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-8 w-8 p-0"
|
||||
onClick={stopPropagation}
|
||||
disabled={isRemoving}
|
||||
>
|
||||
<Trash2 className="w-4 h-4 text-destructive" />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{formatMessage({ id: 'mcp.codex.deleteConfirm.title' }, { name: server.name })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{formatMessage({ id: 'mcp.codex.deleteConfirm.description' }, { name: server.name })}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={isRemoving}>
|
||||
{formatMessage({ id: 'mcp.codex.deleteConfirm.cancel' })}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleRemove}
|
||||
disabled={isRemoving}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{isRemoving ? (
|
||||
<>
|
||||
<span className="animate-spin mr-2">◌</span>
|
||||
{formatMessage({ id: 'mcp.codex.deleteConfirm.deleting' })}
|
||||
</>
|
||||
) : (
|
||||
formatMessage({ id: 'mcp.codex.deleteConfirm.confirm' })
|
||||
)}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* Disabled toggle button (visual only, no edit capability) */}
|
||||
<div className={cn(
|
||||
'w-8 h-8 rounded-md flex items-center justify-center',
|
||||
enabled ? 'bg-green-100 text-green-600' : 'bg-muted text-muted-foreground'
|
||||
)}>
|
||||
{enabled ? <Power className="w-4 h-4" /> : <PowerOff className="w-4 h-4" />}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{isExpanded ? (
|
||||
<ChevronUp className="w-5 h-5 text-muted-foreground" />
|
||||
) : (
|
||||
<ChevronDown className="w-5 h-5 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expanded Content */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-border p-4 space-y-3 bg-muted/30">
|
||||
{/* Command details */}
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.command' })}</p>
|
||||
<code className="text-sm bg-background px-2 py-1 rounded block overflow-x-auto">
|
||||
{server.command}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
{/* Args */}
|
||||
{server.args && server.args.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.args' })}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{server.args.map((arg, idx) => (
|
||||
<Badge key={idx} variant="outline" className="font-mono text-xs">
|
||||
{arg}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Environment variables */}
|
||||
{server.env && Object.keys(server.env).length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.env' })}</p>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(server.env).map(([key, value]) => (
|
||||
<div key={key} className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="secondary" className="font-mono">{key}</Badge>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
|
||||
{value as string}
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Notice based on editable state */}
|
||||
<div className={cn(
|
||||
'flex items-center gap-2 px-3 py-2 rounded-md border',
|
||||
isEditable
|
||||
? 'bg-info/10 border-info/20'
|
||||
: 'bg-muted/50 border-border'
|
||||
)}>
|
||||
{isEditable ? (
|
||||
<>
|
||||
<Edit3 className="w-4 h-4 text-info" />
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatMessage({ id: 'mcp.codex.editableNotice' })}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Lock className="w-4 h-4 text-muted-foreground" />
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatMessage({ id: 'mcp.codex.readOnlyNotice' })}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default CodexMcpEditableCard;
|
||||
247
ccw/frontend/src/components/mcp/ConfigTypeToggle.tsx
Normal file
247
ccw/frontend/src/components/mcp/ConfigTypeToggle.tsx
Normal file
@@ -0,0 +1,247 @@
|
||||
// ========================================
|
||||
// Config Type Toggle Component
|
||||
// ========================================
|
||||
// Toggle between .mcp.json and .claude.json config storage formats with localStorage persistence
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/AlertDialog';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
/**
|
||||
* MCP config file type
|
||||
*/
|
||||
export type McpConfigType = 'mcp-json' | 'claude-json';
|
||||
|
||||
/**
|
||||
* Props for ConfigTypeToggle component
|
||||
*/
|
||||
export interface ConfigTypeToggleProps {
|
||||
/** Current config type */
|
||||
currentType: McpConfigType;
|
||||
/** Callback when config type changes */
|
||||
onTypeChange: (type: McpConfigType) => void;
|
||||
/** Whether to show warning when switching (default: true) */
|
||||
showWarning?: boolean;
|
||||
/** Number of existing servers in current config (for warning message) */
|
||||
existingServersCount?: number;
|
||||
}
|
||||
|
||||
// ========== Constants ==========
|
||||
|
||||
/**
|
||||
* localStorage key for config type persistence
|
||||
*/
|
||||
const CONFIG_TYPE_STORAGE_KEY = 'mcp-config-type';
|
||||
|
||||
/**
|
||||
* Default config type
|
||||
*/
|
||||
const DEFAULT_CONFIG_TYPE: McpConfigType = 'mcp-json';
|
||||
|
||||
// ========== Helper Functions ==========
|
||||
|
||||
/**
|
||||
* Load config type from localStorage
|
||||
*/
|
||||
export function loadConfigType(): McpConfigType {
|
||||
try {
|
||||
const stored = localStorage.getItem(CONFIG_TYPE_STORAGE_KEY);
|
||||
if (stored === 'mcp-json' || stored === 'claude-json') {
|
||||
return stored;
|
||||
}
|
||||
} catch {
|
||||
// Ignore localStorage errors
|
||||
}
|
||||
return DEFAULT_CONFIG_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save config type to localStorage
|
||||
*/
|
||||
export function saveConfigType(type: McpConfigType): void {
|
||||
try {
|
||||
localStorage.setItem(CONFIG_TYPE_STORAGE_KEY, type);
|
||||
} catch {
|
||||
// Ignore localStorage errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file extension for config type
|
||||
*/
|
||||
export function getConfigFileExtension(type: McpConfigType): string {
|
||||
switch (type) {
|
||||
case 'mcp-json':
|
||||
return '.mcp.json';
|
||||
case 'claude-json':
|
||||
return '.claude.json';
|
||||
default:
|
||||
return '.json';
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Component ==========
|
||||
|
||||
/**
|
||||
* Config type toggle segmented control
|
||||
*/
|
||||
export function ConfigTypeToggle({
|
||||
currentType,
|
||||
onTypeChange,
|
||||
showWarning = true,
|
||||
existingServersCount = 0,
|
||||
}: ConfigTypeToggleProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const [internalType, setInternalType] = useState<McpConfigType>(currentType);
|
||||
const [showWarningDialog, setShowWarningDialog] = useState(false);
|
||||
const [pendingType, setPendingType] = useState<McpConfigType | null>(null);
|
||||
|
||||
// Sync internal state with prop changes
|
||||
useEffect(() => {
|
||||
setInternalType(currentType);
|
||||
}, [currentType]);
|
||||
|
||||
// Load saved preference on mount (only if no current type is set)
|
||||
useEffect(() => {
|
||||
if (!currentType || currentType === DEFAULT_CONFIG_TYPE) {
|
||||
const savedType = loadConfigType();
|
||||
if (savedType !== currentType) {
|
||||
setInternalType(savedType);
|
||||
onTypeChange(savedType);
|
||||
}
|
||||
}
|
||||
}, []); // Run once on mount
|
||||
|
||||
// Handle type toggle click
|
||||
const handleTypeClick = (type: McpConfigType) => {
|
||||
if (type === internalType) return;
|
||||
|
||||
if (showWarning && existingServersCount > 0) {
|
||||
setPendingType(type);
|
||||
setShowWarningDialog(true);
|
||||
} else {
|
||||
applyTypeChange(type);
|
||||
}
|
||||
};
|
||||
|
||||
// Apply the type change
|
||||
const applyTypeChange = (type: McpConfigType) => {
|
||||
setInternalType(type);
|
||||
saveConfigType(type);
|
||||
onTypeChange(type);
|
||||
setShowWarningDialog(false);
|
||||
setPendingType(null);
|
||||
};
|
||||
|
||||
// Handle warning dialog confirm
|
||||
const handleWarningConfirm = () => {
|
||||
if (pendingType) {
|
||||
applyTypeChange(pendingType);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle warning dialog cancel
|
||||
const handleWarningCancel = () => {
|
||||
setShowWarningDialog(false);
|
||||
setPendingType(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-3">
|
||||
{/* Label */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{formatMessage({ id: 'mcp.configType.label' })}
|
||||
</span>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{getConfigFileExtension(internalType)}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Toggle Buttons */}
|
||||
<div className="flex gap-2 p-1 bg-muted rounded-lg">
|
||||
<Button
|
||||
variant={internalType === 'mcp-json' ? 'default' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => handleTypeClick('mcp-json')}
|
||||
className={cn(
|
||||
'flex-1',
|
||||
internalType === 'mcp-json' && 'shadow-sm'
|
||||
)}
|
||||
>
|
||||
<span className="text-sm">
|
||||
{formatMessage({ id: 'mcp.configType.claudeJson' })}
|
||||
</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant={internalType === 'claude-json' ? 'default' : 'ghost'}
|
||||
size="sm"
|
||||
onClick={() => handleTypeClick('claude-json')}
|
||||
className={cn(
|
||||
'flex-1',
|
||||
internalType === 'claude-json' && 'shadow-sm'
|
||||
)}
|
||||
>
|
||||
<span className="text-sm">
|
||||
{formatMessage({ id: 'mcp.configType.claudeJson' })}
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Current Format Display */}
|
||||
<div className="flex items-center gap-2 px-3 py-2 bg-muted/50 rounded-md border border-border">
|
||||
<code className="text-xs text-muted-foreground font-mono">
|
||||
{getConfigFileExtension(internalType)}
|
||||
</code>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{formatMessage({ id: 'mcp.configType.' + internalType.replace('-', '') })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Warning Dialog */}
|
||||
<AlertDialog open={showWarningDialog} onOpenChange={setShowWarningDialog}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{formatMessage({ id: 'mcp.configType.switchConfirm' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{formatMessage({ id: 'mcp.configType.switchWarning' })}
|
||||
{existingServersCount > 0 && (
|
||||
<span className="block mt-2 text-amber-600 dark:text-amber-400">
|
||||
{existingServersCount} {formatMessage({ id: 'mcp.stats.total' }).toLowerCase()}
|
||||
</span>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={handleWarningCancel}>
|
||||
{formatMessage({ id: 'mcp.configType.switchCancel' })}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleWarningConfirm}>
|
||||
{formatMessage({ id: 'mcp.configType.switchConfirm' })}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConfigTypeToggle;
|
||||
313
ccw/frontend/src/components/mcp/CrossCliCopyButton.tsx
Normal file
313
ccw/frontend/src/components/mcp/CrossCliCopyButton.tsx
Normal file
@@ -0,0 +1,313 @@
|
||||
// ========================================
|
||||
// Cross-CLI Copy Button Component
|
||||
// ========================================
|
||||
// Button component for copying MCP servers between Claude and Codex configurations
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Copy, ArrowRight, ArrowLeft, RefreshCw } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/Dialog';
|
||||
import { Checkbox } from '@/components/ui/Checkbox';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { useMcpServers } from '@/hooks';
|
||||
import { crossCliCopy } from '@/lib/api';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
export type CliType = 'claude' | 'codex';
|
||||
export type CopyDirection = 'claude-to-codex' | 'codex-to-claude';
|
||||
|
||||
export interface CrossCliCopyButtonProps {
|
||||
/** Current CLI mode */
|
||||
currentMode?: CliType;
|
||||
/** Button variant */
|
||||
variant?: 'default' | 'outline' | 'ghost';
|
||||
/** Button size */
|
||||
size?: 'default' | 'sm' | 'icon';
|
||||
/** Additional class name */
|
||||
className?: string;
|
||||
/** Callback when copy is successful */
|
||||
onSuccess?: (copiedCount: number) => void;
|
||||
}
|
||||
|
||||
interface ServerCheckboxItem {
|
||||
name: string;
|
||||
command: string;
|
||||
enabled: boolean;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
// ========== Constants ==========
|
||||
|
||||
const CLI_LABELS: Record<CliType, string> = {
|
||||
claude: 'Claude',
|
||||
codex: 'Codex',
|
||||
};
|
||||
|
||||
// ========== Component ==========
|
||||
|
||||
export function CrossCliCopyButton({
|
||||
currentMode = 'claude',
|
||||
variant = 'outline',
|
||||
size = 'sm',
|
||||
className,
|
||||
onSuccess,
|
||||
}: CrossCliCopyButtonProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [direction, setDirection] = useState<CopyDirection>(
|
||||
currentMode === 'claude' ? 'claude-to-codex' : 'codex-to-claude'
|
||||
);
|
||||
const [serverItems, setServerItems] = useState<ServerCheckboxItem[]>([]);
|
||||
|
||||
const { servers } = useMcpServers();
|
||||
const [isCopying, setIsCopying] = useState(false);
|
||||
|
||||
// Initialize server items when dialog opens
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
setIsOpen(open);
|
||||
if (open) {
|
||||
setDirection(currentMode === 'claude' ? 'claude-to-codex' : 'codex-to-claude');
|
||||
setServerItems(
|
||||
servers.map((s) => ({
|
||||
name: s.name,
|
||||
command: s.command,
|
||||
enabled: s.enabled,
|
||||
selected: false,
|
||||
}))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Get source and target CLI labels
|
||||
const sourceCli = direction === 'claude-to-codex' ? 'claude' : 'codex';
|
||||
const targetCli = direction === 'claude-to-codex' ? 'codex' : 'claude';
|
||||
|
||||
// Toggle direction
|
||||
const handleToggleDirection = () => {
|
||||
setDirection((prev) =>
|
||||
prev === 'claude-to-codex' ? 'codex-to-claude' : 'claude-to-codex'
|
||||
);
|
||||
setServerItems((prev) => prev.map((item) => ({ ...item, selected: false })));
|
||||
};
|
||||
|
||||
// Toggle server selection
|
||||
const handleToggleServer = (serverName: string) => {
|
||||
setServerItems((prev) =>
|
||||
prev.map((item) =>
|
||||
item.name === serverName ? { ...item, selected: !item.selected } : item
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// Select/deselect all
|
||||
const handleToggleAll = () => {
|
||||
const allSelected = serverItems.every((item) => item.selected);
|
||||
setServerItems((prev) => prev.map((item) => ({ ...item, selected: !allSelected })));
|
||||
};
|
||||
|
||||
// Handle copy operation
|
||||
const handleCopy = async () => {
|
||||
const selectedServers = serverItems.filter((item) => item.selected).map((item) => item.name);
|
||||
|
||||
if (selectedServers.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCopying(true);
|
||||
try {
|
||||
const result = await crossCliCopy({
|
||||
source: sourceCli,
|
||||
target: targetCli,
|
||||
serverNames: selectedServers,
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
onSuccess?.(result.copied.length);
|
||||
setIsOpen(false);
|
||||
|
||||
if (result.failed.length > 0) {
|
||||
console.warn('Some servers failed to copy:', result.failed);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to copy servers:', error);
|
||||
} finally {
|
||||
setIsCopying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const selectedCount = serverItems.filter((item) => item.selected).length;
|
||||
const allSelected = serverItems.length > 0 && serverItems.every((item) => item.selected);
|
||||
const someSelected = serverItems.some((item) => item.selected);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={variant}
|
||||
size={size}
|
||||
onClick={() => handleOpenChange(true)}
|
||||
className={className}
|
||||
>
|
||||
<Copy className="w-4 h-4 mr-2" />
|
||||
{formatMessage({ id: 'mcp.crossCli.button' })}
|
||||
</Button>
|
||||
|
||||
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
||||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Copy className="w-5 h-5" />
|
||||
{formatMessage({ id: 'mcp.crossCli.title' })}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Direction Selector */}
|
||||
<div className="flex items-center justify-between p-4 bg-muted rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="outline" className="text-sm font-mono">
|
||||
{CLI_LABELS[sourceCli]}
|
||||
</Badge>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleToggleDirection}
|
||||
className="p-2"
|
||||
>
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
</Button>
|
||||
<Badge variant="default" className="text-sm font-mono">
|
||||
{CLI_LABELS[targetCli]}
|
||||
</Badge>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleToggleAll}
|
||||
disabled={serverItems.length === 0}
|
||||
>
|
||||
{allSelected
|
||||
? formatMessage({ id: 'common.actions.deselectAll' })
|
||||
: formatMessage({ id: 'common.actions.selectAll' })
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Server Selection List */}
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
{formatMessage(
|
||||
{ id: 'mcp.crossCli.selectServers' },
|
||||
{ source: CLI_LABELS[sourceCli] }
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatMessage({ id: 'mcp.crossCli.selectServersHint' })}
|
||||
</p>
|
||||
|
||||
{serverItems.length === 0 ? (
|
||||
<div className="p-4 text-center text-muted-foreground text-sm border border-dashed rounded-lg">
|
||||
{formatMessage({ id: 'mcp.crossCli.noServers' })}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-1 max-h-60 overflow-y-auto">
|
||||
{serverItems.map((server) => (
|
||||
<div
|
||||
key={server.name}
|
||||
className={cn(
|
||||
'flex items-center gap-3 p-2 rounded-lg transition-colors cursor-pointer',
|
||||
server.selected ? 'bg-primary/10' : 'hover:bg-muted/50'
|
||||
)}
|
||||
onClick={() => handleToggleServer(server.name)}
|
||||
>
|
||||
<Checkbox
|
||||
id={`server-${server.name}`}
|
||||
checked={server.selected}
|
||||
onChange={() => handleToggleServer(server.name)}
|
||||
className="w-4 h-4"
|
||||
/>
|
||||
<label
|
||||
htmlFor={`server-${server.name}`}
|
||||
className="flex-1 cursor-pointer min-w-0"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-foreground truncate">
|
||||
{server.name}
|
||||
</span>
|
||||
{server.enabled && (
|
||||
<Badge variant="success" className="text-xs">
|
||||
{formatMessage({ id: 'mcp.status.enabled' })}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-mono truncate">
|
||||
{server.command}
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Selection Summary */}
|
||||
{someSelected && (
|
||||
<div className="flex items-center justify-between p-3 bg-primary/5 rounded-lg border border-primary/20">
|
||||
<span className="text-sm text-foreground">
|
||||
{formatMessage(
|
||||
{ id: 'mcp.crossCli.selectedCount' },
|
||||
{ count: selectedCount }
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => handleOpenChange(false)}
|
||||
disabled={isCopying}
|
||||
>
|
||||
{formatMessage({ id: 'common.actions.cancel' })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCopy}
|
||||
disabled={selectedCount === 0 || isCopying}
|
||||
>
|
||||
{isCopying ? (
|
||||
<>
|
||||
<span className="animate-spin mr-2">-</span>
|
||||
{formatMessage({ id: 'mcp.crossCli.copying' })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{direction === 'claude-to-codex' ? (
|
||||
<ArrowRight className="w-4 h-4 mr-2" />
|
||||
) : (
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
{formatMessage(
|
||||
{ id: 'mcp.crossCli.copyButton' },
|
||||
{ count: selectedCount, target: CLI_LABELS[targetCli] }
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CrossCliCopyButton;
|
||||
159
ccw/frontend/src/components/mcp/EnterpriseMcpBadge.tsx
Normal file
159
ccw/frontend/src/components/mcp/EnterpriseMcpBadge.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
// ========================================
|
||||
// Enterprise MCP Badge Component
|
||||
// ========================================
|
||||
// Badge component for enterprise MCP server identification
|
||||
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Building2, Crown } from 'lucide-react';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
export interface EnterpriseMcpBadgeProps {
|
||||
/** Server configuration to check for enterprise status */
|
||||
server?: {
|
||||
name: string;
|
||||
command?: string;
|
||||
args?: string[];
|
||||
env?: Record<string, string>;
|
||||
};
|
||||
/** Additional class name */
|
||||
className?: string;
|
||||
/** Badge variant */
|
||||
variant?: 'default' | 'subtle' | 'icon-only';
|
||||
/** Enterprise server patterns for detection */
|
||||
enterprisePatterns?: string[];
|
||||
}
|
||||
|
||||
// ========== Constants ==========
|
||||
|
||||
// Default patterns that indicate enterprise MCP servers
|
||||
const DEFAULT_ENTERPRISE_PATTERNS = [
|
||||
// Command patterns
|
||||
'^enterprise-',
|
||||
'-enterprise$',
|
||||
'^ent-',
|
||||
'claude-.*enterprise',
|
||||
'anthropic-.*',
|
||||
// Server name patterns
|
||||
'^claude.*enterprise',
|
||||
'^anthropic',
|
||||
'^enterprise',
|
||||
// Env var patterns (API keys, endpoints)
|
||||
'ANTHROPIC.*',
|
||||
'ENTERPRISE.*',
|
||||
'.*_ENTERPRISE_ENDPOINT',
|
||||
];
|
||||
|
||||
// ========== Helper Functions ==========
|
||||
|
||||
/**
|
||||
* Check if a server matches enterprise patterns
|
||||
*/
|
||||
function isEnterpriseServer(
|
||||
server: EnterpriseMcpBadgeProps['server'],
|
||||
patterns: string[]
|
||||
): boolean {
|
||||
if (!server) return false;
|
||||
|
||||
const allPatterns = [...patterns, ...DEFAULT_ENTERPRISE_PATTERNS];
|
||||
const searchText = [
|
||||
server.name,
|
||||
server.command || '',
|
||||
...(server.args || []),
|
||||
...Object.keys(server.env || {}),
|
||||
...Object.values(server.env || {}),
|
||||
]
|
||||
.join(' ')
|
||||
.toLowerCase();
|
||||
|
||||
return allPatterns.some((pattern) => {
|
||||
try {
|
||||
const regex = new RegExp(pattern, 'i');
|
||||
return regex.test(searchText);
|
||||
} catch {
|
||||
// Fallback to simple string match if regex is invalid
|
||||
return searchText.includes(pattern.toLowerCase());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ========== Component ==========
|
||||
|
||||
export function EnterpriseMcpBadge({
|
||||
server,
|
||||
className,
|
||||
variant = 'default',
|
||||
enterprisePatterns = [],
|
||||
}: EnterpriseMcpBadgeProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const isEnterprise = isEnterpriseServer(server, enterprisePatterns);
|
||||
|
||||
if (!isEnterprise || !server) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Icon-only variant
|
||||
if (variant === 'icon-only') {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'inline-flex items-center justify-center w-5 h-5 rounded-full',
|
||||
'bg-gradient-to-br from-amber-500 to-orange-600',
|
||||
'text-white shadow-sm',
|
||||
className
|
||||
)}
|
||||
title={formatMessage({ id: 'mcp.enterprise.tooltip' })}
|
||||
>
|
||||
<Crown className="w-3 h-3" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Subtle variant (smaller, less prominent)
|
||||
if (variant === 'subtle') {
|
||||
return (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={cn(
|
||||
'text-xs border-amber-500/30 text-amber-600 dark:text-amber-400',
|
||||
'bg-amber-500/10',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Building2 className="w-3 h-3 mr-1" />
|
||||
{formatMessage({ id: 'mcp.enterprise.label' })}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
// Default variant (prominent)
|
||||
return (
|
||||
<Badge
|
||||
className={cn(
|
||||
'bg-gradient-to-r from-amber-500 to-orange-600 text-white border-0',
|
||||
'shadow-sm',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Crown className="w-3 h-3 mr-1" />
|
||||
{formatMessage({ id: 'mcp.enterprise.label' })}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
// ========== Hook for Enterprise Detection ==========
|
||||
|
||||
/**
|
||||
* Hook to check if a server is an enterprise server
|
||||
*/
|
||||
export function useEnterpriseServer(
|
||||
server: EnterpriseMcpBadgeProps['server'] | undefined,
|
||||
customPatterns: string[] = []
|
||||
): boolean {
|
||||
return isEnterpriseServer(server, customPatterns);
|
||||
}
|
||||
|
||||
export default EnterpriseMcpBadge;
|
||||
283
ccw/frontend/src/components/mcp/InstallCommandDialog.tsx
Normal file
283
ccw/frontend/src/components/mcp/InstallCommandDialog.tsx
Normal file
@@ -0,0 +1,283 @@
|
||||
// ========================================
|
||||
// Install Command Dialog Component
|
||||
// ========================================
|
||||
// Dialog for generating and displaying MCP server install commands
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Copy, Check, Terminal, Download } from 'lucide-react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/Dialog';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
export interface InstallCommandDialogProps {
|
||||
/** Server configuration to generate command for */
|
||||
server?: {
|
||||
name: string;
|
||||
command: string;
|
||||
args?: string[];
|
||||
env?: Record<string, string>;
|
||||
};
|
||||
/** Whether dialog is open */
|
||||
open: boolean;
|
||||
/** Callback when dialog is closed */
|
||||
onClose: () => void;
|
||||
/** Installation scope */
|
||||
scope?: 'project' | 'global';
|
||||
/** Config type (mcp for .mcp.json, claude for .claude.json) */
|
||||
configType?: 'mcp' | 'claude';
|
||||
}
|
||||
|
||||
// ========== Component ==========
|
||||
|
||||
export function InstallCommandDialog({
|
||||
server,
|
||||
open,
|
||||
onClose,
|
||||
scope = 'project',
|
||||
configType = 'mcp',
|
||||
}: InstallCommandDialogProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
// Generate install command
|
||||
const generateCommand = (): string => {
|
||||
if (!server) return '';
|
||||
|
||||
const envArgs = server.env
|
||||
? Object.entries(server.env).flatMap(([key, value]) => ['--env', `${key}=${value}`])
|
||||
: [];
|
||||
|
||||
const args = [
|
||||
'ccw',
|
||||
'mcp',
|
||||
'install',
|
||||
server.name,
|
||||
'--command',
|
||||
server.command,
|
||||
...(server.args || []).flatMap((arg) => ['--args', arg]),
|
||||
...envArgs,
|
||||
'--scope',
|
||||
scope,
|
||||
];
|
||||
|
||||
if (configType === 'claude') {
|
||||
args.push('--config-type', 'claude');
|
||||
}
|
||||
|
||||
return args.join(' ');
|
||||
};
|
||||
|
||||
// Generate JSON config snippet
|
||||
const generateJsonConfig = (): string => {
|
||||
if (!server) return '';
|
||||
|
||||
const config = {
|
||||
mcpServers: {
|
||||
[server.name]: {
|
||||
command: server.command,
|
||||
...(server.args && { args: server.args }),
|
||||
...(server.env && { env: server.env }),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return JSON.stringify(config, null, 2);
|
||||
};
|
||||
|
||||
// Handle copy to clipboard
|
||||
const handleCopy = async (text: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (error) {
|
||||
console.error('Failed to copy:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const command = generateCommand();
|
||||
const jsonConfig = generateJsonConfig();
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Terminal className="w-5 h-5" />
|
||||
{formatMessage({ id: 'mcp.installCmd.title' })}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{server && (
|
||||
<div className="space-y-4">
|
||||
{/* Server Info */}
|
||||
<div className="p-3 bg-muted rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-sm font-medium text-foreground">{server.name}</span>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{scope}
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="text-xs font-mono">
|
||||
.{configType === 'mcp' ? 'mcp.json' : 'claude.json'}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{server.command} {(server.args || []).join(' ')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* CLI Command */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium text-foreground flex items-center gap-2">
|
||||
<Terminal className="w-4 h-4" />
|
||||
{formatMessage({ id: 'mcp.installCmd.cliCommand' })}
|
||||
</label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleCopy(command)}
|
||||
className="h-7"
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check className="w-3 h-3 mr-1" />
|
||||
{formatMessage({ id: 'common.success.copied' })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="w-3 h-3 mr-1" />
|
||||
{formatMessage({ id: 'common.actions.copy' })}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<pre
|
||||
className={cn(
|
||||
'bg-slate-950 text-slate-50 p-4 rounded-lg text-sm font-mono overflow-x-auto',
|
||||
'border border-slate-800'
|
||||
)}
|
||||
>
|
||||
<code>{command}</code>
|
||||
</pre>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatMessage({ id: 'mcp.installCmd.cliCommandHint' })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* JSON Config */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium text-foreground flex items-center gap-2">
|
||||
<Download className="w-4 h-4" />
|
||||
{formatMessage({ id: 'mcp.installCmd.jsonConfig' })}
|
||||
</label>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleCopy(jsonConfig)}
|
||||
className="h-7"
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check className="w-3 h-3 mr-1" />
|
||||
{formatMessage({ id: 'common.success.copied' })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="w-3 h-3 mr-1" />
|
||||
{formatMessage({ id: 'common.actions.copy' })}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<pre
|
||||
className={cn(
|
||||
'bg-slate-950 text-slate-50 p-4 rounded-lg text-sm font-mono overflow-x-auto',
|
||||
'border border-slate-800'
|
||||
)}
|
||||
>
|
||||
<code>{jsonConfig}</code>
|
||||
</pre>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatMessage(
|
||||
{ id: 'mcp.installCmd.jsonConfigHint' },
|
||||
{ filename: `.${configType === 'mcp' ? 'mcp' : 'claude'}.json` }
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Environment Variables */}
|
||||
{server.env && Object.keys(server.env).length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
{formatMessage({ id: 'mcp.installCmd.envVars' })}
|
||||
</label>
|
||||
<div className="p-3 bg-muted rounded-lg space-y-1">
|
||||
{Object.entries(server.env).map(([key, value]) => (
|
||||
<div key={key} className="flex items-center gap-2 text-sm font-mono">
|
||||
<span className="text-foreground font-medium">{key}=</span>
|
||||
<span className="text-muted-foreground break-all">{value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Installation Steps */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
{formatMessage({ id: 'mcp.installCmd.steps' })}
|
||||
</label>
|
||||
<ol className="space-y-2 text-sm text-muted-foreground">
|
||||
<li className="flex gap-2">
|
||||
<span className="flex-shrink-0 w-5 h-5 flex items-center justify-center bg-primary/20 text-primary rounded-full text-xs font-medium">
|
||||
1
|
||||
</span>
|
||||
<span>{formatMessage({ id: 'mcp.installCmd.step1' })}</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="flex-shrink-0 w-5 h-5 flex items-center justify-center bg-primary/20 text-primary rounded-full text-xs font-medium">
|
||||
2
|
||||
</span>
|
||||
<span>
|
||||
{formatMessage({ id: 'mcp.installCmd.step2' })}
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex gap-2">
|
||||
<span className="flex-shrink-0 w-5 h-5 flex items-center justify-center bg-primary/20 text-primary rounded-full text-xs font-medium">
|
||||
3
|
||||
</span>
|
||||
<span>
|
||||
{formatMessage({ id: 'mcp.installCmd.step3' })}
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end pt-4 border-t border-border">
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
{formatMessage({ id: 'common.actions.close' })}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default InstallCommandDialog;
|
||||
@@ -1,7 +1,7 @@
|
||||
// ========================================
|
||||
// MCP Server Dialog Component
|
||||
// ========================================
|
||||
// Add/Edit dialog for MCP server configuration with template presets
|
||||
// Add/Edit dialog for MCP server configuration with dynamic template loading
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
fetchMcpServers,
|
||||
type McpServer,
|
||||
} from '@/lib/api';
|
||||
import { mcpServersKeys } from '@/hooks';
|
||||
import { mcpServersKeys, useMcpTemplates } from '@/hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// ========== Types ==========
|
||||
@@ -42,14 +42,8 @@ export interface McpServerDialogProps {
|
||||
onSave?: () => void;
|
||||
}
|
||||
|
||||
export interface McpTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
command: string;
|
||||
args: string[];
|
||||
env?: Record<string, string>;
|
||||
}
|
||||
// Re-export McpTemplate for convenience
|
||||
export type { McpTemplate } from '@/types/store';
|
||||
|
||||
interface McpServerFormData {
|
||||
name: string;
|
||||
@@ -67,32 +61,6 @@ interface FormErrors {
|
||||
env?: string;
|
||||
}
|
||||
|
||||
// ========== Template Presets ==========
|
||||
|
||||
const TEMPLATE_PRESETS: McpTemplate[] = [
|
||||
{
|
||||
id: 'npx-stdio',
|
||||
name: 'NPX STDIO',
|
||||
description: 'Node.js package using stdio transport',
|
||||
command: 'npx',
|
||||
args: ['{package}'],
|
||||
},
|
||||
{
|
||||
id: 'python-stdio',
|
||||
name: 'Python STDIO',
|
||||
description: 'Python script using stdio transport',
|
||||
command: 'python',
|
||||
args: ['{script}.py'],
|
||||
},
|
||||
{
|
||||
id: 'sse-server',
|
||||
name: 'SSE Server',
|
||||
description: 'HTTP server with Server-Sent Events transport',
|
||||
command: 'node',
|
||||
args: ['{server}.js'],
|
||||
},
|
||||
];
|
||||
|
||||
// ========== Component ==========
|
||||
|
||||
export function McpServerDialog({
|
||||
@@ -105,6 +73,9 @@ export function McpServerDialog({
|
||||
const { formatMessage } = useIntl();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Fetch templates from backend
|
||||
const { templates, isLoading: templatesLoading } = useMcpTemplates();
|
||||
|
||||
// Form state
|
||||
const [formData, setFormData] = useState<McpServerFormData>({
|
||||
name: '',
|
||||
@@ -181,17 +152,17 @@ export function McpServerDialog({
|
||||
};
|
||||
|
||||
const handleTemplateSelect = (templateId: string) => {
|
||||
const template = TEMPLATE_PRESETS.find((t) => t.id === templateId);
|
||||
const template = templates.find((t) => t.name === templateId);
|
||||
if (template) {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
command: template.command,
|
||||
args: template.args,
|
||||
env: template.env || {},
|
||||
command: template.serverConfig.command,
|
||||
args: template.serverConfig.args || [],
|
||||
env: template.serverConfig.env || {},
|
||||
}));
|
||||
setArgsInput(template.args.join(', '));
|
||||
setArgsInput((template.serverConfig.args || []).join(', '));
|
||||
setEnvInput(
|
||||
Object.entries(template.env || {})
|
||||
Object.entries(template.serverConfig.env || {})
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.join('\n')
|
||||
);
|
||||
@@ -324,23 +295,32 @@ export function McpServerDialog({
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
{formatMessage({ id: 'mcp.dialog.form.template' })}
|
||||
</label>
|
||||
<Select value={selectedTemplate} onValueChange={handleTemplateSelect}>
|
||||
<Select value={selectedTemplate} onValueChange={handleTemplateSelect} disabled={templatesLoading}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue
|
||||
placeholder={formatMessage({ id: 'mcp.dialog.form.templatePlaceholder' })}
|
||||
placeholder={templatesLoading
|
||||
? formatMessage({ id: 'mcp.templates.loading' })
|
||||
: formatMessage({ id: 'mcp.dialog.form.templatePlaceholder' })
|
||||
}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{TEMPLATE_PRESETS.map((template) => (
|
||||
<SelectItem key={template.id} value={template.id}>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{template.name}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{template.description}
|
||||
</span>
|
||||
</div>
|
||||
{templates.length === 0 ? (
|
||||
<SelectItem value="" disabled>
|
||||
{formatMessage({ id: 'mcp.templates.empty.title' })}
|
||||
</SelectItem>
|
||||
))}
|
||||
) : (
|
||||
templates.map((template) => (
|
||||
<SelectItem key={template.name} value={template.name}>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{template.name}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{template.description || formatMessage({ id: 'mcp.dialog.form.templatePlaceholder' })}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
507
ccw/frontend/src/components/mcp/McpTemplatesSection.tsx
Normal file
507
ccw/frontend/src/components/mcp/McpTemplatesSection.tsx
Normal file
@@ -0,0 +1,507 @@
|
||||
// ========================================
|
||||
// MCP Templates Section Component
|
||||
// ========================================
|
||||
// Template management component with card/list view, search input, category filter, save/delete/install actions
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
Copy,
|
||||
Trash2,
|
||||
Download,
|
||||
Search,
|
||||
Filter,
|
||||
Plus,
|
||||
FileCode,
|
||||
Folder,
|
||||
Globe,
|
||||
} from 'lucide-react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/Select';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/Dialog';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/AlertDialog';
|
||||
import {
|
||||
fetchMcpTemplateCategories,
|
||||
searchMcpTemplates,
|
||||
fetchMcpTemplatesByCategory,
|
||||
} from '@/lib/api';
|
||||
import { mcpTemplatesKeys, useDeleteTemplate, useInstallTemplate } from '@/hooks';
|
||||
import type { McpTemplate } from '@/types/store';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
export interface McpTemplatesSectionProps {
|
||||
/** Callback when template is installed (opens McpServerDialog) */
|
||||
onInstallTemplate?: (template: McpTemplate) => void;
|
||||
/** Callback when current server should be saved as template */
|
||||
onSaveAsTemplate?: (serverName: string, config: { command: string; args: string[]; env?: Record<string, string> }) => void;
|
||||
}
|
||||
|
||||
interface TemplateSaveDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (name: string, category: string, description: string) => void;
|
||||
defaultName?: string;
|
||||
defaultCommand?: string;
|
||||
defaultArgs?: string[];
|
||||
}
|
||||
|
||||
interface TemplateCardProps {
|
||||
template: McpTemplate;
|
||||
onInstall: (template: McpTemplate) => void;
|
||||
onDelete: (templateName: string) => void;
|
||||
isInstalling: boolean;
|
||||
isDeleting: boolean;
|
||||
}
|
||||
|
||||
// ========== Constants ==========
|
||||
|
||||
const SEARCH_DEBOUNCE_MS = 300;
|
||||
|
||||
// ========== Helper Components ==========
|
||||
|
||||
/**
|
||||
* Template Card Component - Display single template with actions
|
||||
*/
|
||||
function TemplateCard({ template, onInstall, onDelete, isInstalling, isDeleting }: TemplateCardProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const handleInstall = () => {
|
||||
onInstall(template);
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
onDelete(template.name);
|
||||
};
|
||||
|
||||
// Get icon based on category
|
||||
const getCategoryIcon = () => {
|
||||
const iconProps = { className: 'w-4 h-4' };
|
||||
switch (template.category?.toLowerCase()) {
|
||||
case 'stdio':
|
||||
case 'transport':
|
||||
return <FileCode {...iconProps} />;
|
||||
case 'language':
|
||||
case 'python':
|
||||
case 'node':
|
||||
return <Folder {...iconProps} />;
|
||||
case 'official':
|
||||
case 'builtin':
|
||||
return <Globe {...iconProps} />;
|
||||
default:
|
||||
return <Copy {...iconProps} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-4 hover:border-primary/50 transition-colors">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-start gap-3 flex-1 min-w-0">
|
||||
<div className="p-2 rounded-lg bg-muted flex-shrink-0">
|
||||
{getCategoryIcon()}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className="text-sm font-medium text-foreground truncate">
|
||||
{template.name}
|
||||
</span>
|
||||
{template.category && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{template.category}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{template.description && (
|
||||
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">
|
||||
{template.description}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center gap-2 mt-2">
|
||||
<code className="text-xs bg-muted px-2 py-1 rounded font-mono truncate max-w-[150px]">
|
||||
{template.serverConfig.command}
|
||||
</code>
|
||||
{template.serverConfig.args && template.serverConfig.args.length > 0 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{template.serverConfig.args.length} args
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 flex-shrink-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleInstall}
|
||||
disabled={isInstalling}
|
||||
title={formatMessage({ id: 'mcp.templates.actions.install' })}
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleDelete}
|
||||
disabled={isDeleting}
|
||||
title={formatMessage({ id: 'mcp.templates.actions.delete' })}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Template Save Dialog - Save current server configuration as template
|
||||
*/
|
||||
function TemplateSaveDialog({
|
||||
open,
|
||||
onClose,
|
||||
onSave,
|
||||
defaultName = '',
|
||||
}: TemplateSaveDialogProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const [name, setName] = useState(defaultName);
|
||||
const [category, setCategory] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [errors, setErrors] = useState<{ name?: string }>({});
|
||||
|
||||
// Reset form when dialog opens
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setName(defaultName || '');
|
||||
setCategory('');
|
||||
setDescription('');
|
||||
setErrors({});
|
||||
}
|
||||
}, [open, defaultName]);
|
||||
|
||||
const handleSave = () => {
|
||||
if (!name.trim()) {
|
||||
setErrors({ name: formatMessage({ id: 'mcp.templates.saveDialog.validation.nameRequired' }) });
|
||||
return;
|
||||
}
|
||||
onSave(name.trim(), category.trim(), description.trim());
|
||||
setName('');
|
||||
setCategory('');
|
||||
setDescription('');
|
||||
setErrors({});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{formatMessage({ id: 'mcp.templates.saveDialog.title' })}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
{/* Name */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
{formatMessage({ id: 'mcp.templates.saveDialog.name' })}
|
||||
<span className="text-destructive ml-1">*</span>
|
||||
</label>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={(e) => {
|
||||
setName(e.target.value);
|
||||
if (errors.name) setErrors({});
|
||||
}}
|
||||
placeholder={formatMessage({ id: 'mcp.templates.saveDialog.namePlaceholder' })}
|
||||
error={!!errors.name}
|
||||
/>
|
||||
{errors.name && (
|
||||
<p className="text-sm text-destructive">{errors.name}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Category */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
{formatMessage({ id: 'mcp.templates.saveDialog.category' })}
|
||||
</label>
|
||||
<Select value={category} onValueChange={setCategory}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={formatMessage({ id: 'mcp.templates.saveDialog.categoryPlaceholder' })} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="stdio">STDIO</SelectItem>
|
||||
<SelectItem value="sse">SSE</SelectItem>
|
||||
<SelectItem value="language">Language</SelectItem>
|
||||
<SelectItem value="official">Official</SelectItem>
|
||||
<SelectItem value="custom">Custom</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-foreground">
|
||||
{formatMessage({ id: 'mcp.templates.saveDialog.description' })}
|
||||
</label>
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder={formatMessage({ id: 'mcp.templates.saveDialog.descriptionPlaceholder' })}
|
||||
className="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
{formatMessage({ id: 'mcp.templates.saveDialog.cancel' })}
|
||||
</Button>
|
||||
<Button onClick={handleSave}>
|
||||
{formatMessage({ id: 'mcp.templates.saveDialog.save' })}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
// ========== Main Component ==========
|
||||
|
||||
/**
|
||||
* McpTemplatesSection - Main template management component
|
||||
*
|
||||
* Features:
|
||||
* - Template list view with search and category filter
|
||||
* - Install template action (populates McpServerDialog)
|
||||
* - Delete template with confirmation
|
||||
* - Save current server as template
|
||||
*/
|
||||
export function McpTemplatesSection({ onInstallTemplate, onSaveAsTemplate }: McpTemplatesSectionProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
// State
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [debouncedSearch, setDebouncedSearch] = useState('');
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>('all');
|
||||
const [saveDialogOpen, setSaveDialogOpen] = useState(false);
|
||||
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
||||
const [templateToDelete, setTemplateToDelete] = useState<string | null>(null);
|
||||
|
||||
// Fetch categories for filter dropdown
|
||||
const { data: categories = [], isLoading: categoriesLoading } = useQuery({
|
||||
queryKey: mcpTemplatesKeys.categories(),
|
||||
queryFn: fetchMcpTemplateCategories,
|
||||
staleTime: 10 * 60 * 1000, // 10 minutes
|
||||
});
|
||||
|
||||
// Mutations
|
||||
const deleteMutation = useDeleteTemplate();
|
||||
const installMutation = useInstallTemplate();
|
||||
|
||||
// Debounced search
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedSearch(searchQuery);
|
||||
}, SEARCH_DEBOUNCE_MS);
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchQuery]);
|
||||
|
||||
// Fetch templates based on search and category filter
|
||||
const { data: templates = [], isLoading: templatesLoading } = useQuery({
|
||||
queryKey: mcpTemplatesKeys.list(selectedCategory === 'all' ? undefined : selectedCategory),
|
||||
queryFn: async () => {
|
||||
if (debouncedSearch.trim()) {
|
||||
return searchMcpTemplates(debouncedSearch.trim());
|
||||
} else if (selectedCategory === 'all') {
|
||||
// Fetch all templates by iterating categories
|
||||
const allTemplates: McpTemplate[] = [];
|
||||
for (const category of categories) {
|
||||
const categoryTemplates = await fetchMcpTemplatesByCategory(category);
|
||||
allTemplates.push(...categoryTemplates);
|
||||
}
|
||||
return allTemplates;
|
||||
} else {
|
||||
return fetchMcpTemplatesByCategory(selectedCategory);
|
||||
}
|
||||
},
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
enabled: !categoriesLoading,
|
||||
});
|
||||
|
||||
// Handlers
|
||||
const handleInstallTemplate = useCallback((template: McpTemplate) => {
|
||||
// Call parent callback to open McpServerDialog with template data
|
||||
onInstallTemplate?.(template);
|
||||
}, [onInstallTemplate]);
|
||||
|
||||
const handleDeleteClick = useCallback((templateName: string) => {
|
||||
setTemplateToDelete(templateName);
|
||||
setDeleteConfirmOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleDeleteConfirm = useCallback(async () => {
|
||||
if (!templateToDelete) return;
|
||||
|
||||
const result = await deleteMutation.deleteTemplate(templateToDelete);
|
||||
if (result.success) {
|
||||
setDeleteConfirmOpen(false);
|
||||
setTemplateToDelete(null);
|
||||
}
|
||||
}, [templateToDelete, deleteMutation]);
|
||||
|
||||
const handleSaveTemplate = useCallback((_name: string, _category: string, _description: string) => {
|
||||
onSaveAsTemplate?.(_name, { command: '', args: [] });
|
||||
setSaveDialogOpen(false);
|
||||
}, [onSaveAsTemplate]);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Header with search and filter */}
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
{/* Search Input */}
|
||||
<div className="relative flex-1 max-w-md">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
||||
<Input
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder={formatMessage({ id: 'mcp.templates.searchPlaceholder' })}
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Category Filter */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Filter className="w-4 h-4 text-muted-foreground" />
|
||||
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
|
||||
<SelectTrigger className="w-[150px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">
|
||||
{formatMessage({ id: 'mcp.templates.filter.allCategories' })}
|
||||
</SelectItem>
|
||||
{categories.map((category) => (
|
||||
<SelectItem key={category} value={category}>
|
||||
{category}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Save Template Button */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setSaveDialogOpen(true)}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-1" />
|
||||
{formatMessage({ id: 'mcp.templates.actions.saveAsTemplate' })}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Template List */}
|
||||
{templatesLoading ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{formatMessage({ id: 'mcp.templates.loading' })}
|
||||
</p>
|
||||
</div>
|
||||
) : templates.length === 0 ? (
|
||||
<Card className="p-12 text-center">
|
||||
<Copy className="w-12 h-12 mx-auto text-muted-foreground mb-4" />
|
||||
<h3 className="text-lg font-medium text-foreground mb-2">
|
||||
{formatMessage({ id: 'mcp.templates.empty.title' })}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
{formatMessage({ id: 'mcp.templates.empty.message' })}
|
||||
</p>
|
||||
<Button onClick={() => setSaveDialogOpen(true)}>
|
||||
<Plus className="w-4 h-4 mr-1" />
|
||||
{formatMessage({ id: 'mcp.templates.empty.createFirst' })}
|
||||
</Button>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
|
||||
{templates.map((template) => (
|
||||
<TemplateCard
|
||||
key={template.name}
|
||||
template={template}
|
||||
onInstall={handleInstallTemplate}
|
||||
onDelete={handleDeleteClick}
|
||||
isInstalling={installMutation.isInstalling}
|
||||
isDeleting={deleteMutation.isDeleting}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Save Template Dialog */}
|
||||
<TemplateSaveDialog
|
||||
open={saveDialogOpen}
|
||||
onClose={() => setSaveDialogOpen(false)}
|
||||
onSave={handleSaveTemplate}
|
||||
/>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<AlertDialog open={deleteConfirmOpen} onOpenChange={setDeleteConfirmOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
{formatMessage({ id: 'mcp.templates.deleteDialog.title' })}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{formatMessage(
|
||||
{ id: 'mcp.templates.deleteDialog.message' },
|
||||
{ name: templateToDelete }
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>
|
||||
{formatMessage({ id: 'mcp.templates.deleteDialog.cancel' })}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleDeleteConfirm}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{deleteMutation.isDeleting
|
||||
? formatMessage({ id: 'mcp.templates.deleteDialog.deleting' })
|
||||
: formatMessage({ id: 'mcp.templates.deleteDialog.delete' })
|
||||
}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default McpTemplatesSection;
|
||||
239
ccw/frontend/src/components/mcp/OtherProjectsSection.tsx
Normal file
239
ccw/frontend/src/components/mcp/OtherProjectsSection.tsx
Normal file
@@ -0,0 +1,239 @@
|
||||
// ========================================
|
||||
// Other Projects Section Component
|
||||
// ========================================
|
||||
// Section for discovering and importing servers from other projects
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { FolderOpen, Copy, ChevronRight, RefreshCw } from 'lucide-react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/Select';
|
||||
import { useProjectOperations, useMcpServerMutations } from '@/hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { McpServer } from '@/lib/api';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
export interface OtherProjectServer extends McpServer {
|
||||
projectPath: string;
|
||||
projectName: string;
|
||||
}
|
||||
|
||||
export interface OtherProjectsSectionProps {
|
||||
/** Callback when server is successfully imported */
|
||||
onImportSuccess?: (serverName: string, sourceProject: string) => void;
|
||||
/** Additional class name */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// ========== Component ==========
|
||||
|
||||
export function OtherProjectsSection({
|
||||
onImportSuccess,
|
||||
className,
|
||||
}: OtherProjectsSectionProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const [selectedProjectPath, setSelectedProjectPath] = useState<string | null>(null);
|
||||
const [otherServers, setOtherServers] = useState<OtherProjectServer[]>([]);
|
||||
const [isFetchingServers, setIsFetchingServers] = useState(false);
|
||||
|
||||
const { projects, currentProject, fetchOtherServers, isFetchingServers: isGlobalFetching } =
|
||||
useProjectOperations();
|
||||
const { createServer, isCreating } = useMcpServerMutations();
|
||||
|
||||
// Get available projects (excluding current)
|
||||
const availableProjects = projects.filter((p) => p !== currentProject);
|
||||
|
||||
// Handle project selection
|
||||
const handleProjectSelect = async (projectPath: string) => {
|
||||
setSelectedProjectPath(projectPath);
|
||||
setIsFetchingServers(true);
|
||||
|
||||
try {
|
||||
const response = await fetchOtherServers([projectPath]);
|
||||
const servers: OtherProjectServer[] = [];
|
||||
|
||||
for (const [path, serverList] of Object.entries(response.servers)) {
|
||||
const projectName = path.split(/[/\\]/).filter(Boolean).pop() || path;
|
||||
for (const server of (serverList as McpServer[])) {
|
||||
servers.push({
|
||||
...server,
|
||||
projectPath: path,
|
||||
projectName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setOtherServers(servers);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch other projects servers:', error);
|
||||
setOtherServers([]);
|
||||
} finally {
|
||||
setIsFetchingServers(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle server import
|
||||
const handleImportServer = async (server: OtherProjectServer) => {
|
||||
try {
|
||||
// Generate a unique name by combining project name and server name
|
||||
const uniqueName = `${server.projectName}-${server.name}`.toLowerCase().replace(/\s+/g, '-');
|
||||
|
||||
await createServer({
|
||||
command: server.command,
|
||||
args: server.args,
|
||||
env: server.env,
|
||||
scope: 'project',
|
||||
enabled: server.enabled,
|
||||
});
|
||||
|
||||
onImportSuccess?.(uniqueName, server.projectPath);
|
||||
} catch (error) {
|
||||
console.error('Failed to import server:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const isLoading = isFetchingServers || isGlobalFetching || isCreating;
|
||||
const selectedProjectName = selectedProjectPath
|
||||
? selectedProjectPath.split(/[/\\]/).filter(Boolean).pop()
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Card className={cn('p-4', className)}>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<FolderOpen className="w-5 h-5 text-primary" />
|
||||
<h3 className="text-sm font-medium text-foreground">
|
||||
{formatMessage({ id: 'mcp.otherProjects.title' })}
|
||||
</h3>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => selectedProjectPath && handleProjectSelect(selectedProjectPath)}
|
||||
disabled={!selectedProjectPath || isLoading}
|
||||
className="h-8"
|
||||
>
|
||||
<RefreshCw className={cn('w-4 h-4', isLoading && 'animate-spin')} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-xs text-muted-foreground mb-4">
|
||||
{formatMessage({ id: 'mcp.otherProjects.description' })}
|
||||
</p>
|
||||
|
||||
{/* Project Selector */}
|
||||
<div className="mb-4">
|
||||
<label className="text-sm font-medium text-foreground mb-2 block">
|
||||
{formatMessage({ id: 'mcp.otherProjects.selectProject' })}
|
||||
</label>
|
||||
<Select value={selectedProjectPath ?? ''} onValueChange={handleProjectSelect}>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue
|
||||
placeholder={formatMessage({ id: 'mcp.otherProjects.selectProjectPlaceholder' })}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{availableProjects.length === 0 ? (
|
||||
<div className="p-2 text-sm text-muted-foreground text-center">
|
||||
{formatMessage({ id: 'mcp.otherProjects.noProjects' })}
|
||||
</div>
|
||||
) : (
|
||||
availableProjects.map((path) => {
|
||||
const name = path.split(/[/\\]/).filter(Boolean).pop() || path;
|
||||
return (
|
||||
<SelectItem key={path} value={path}>
|
||||
<div className="flex items-center gap-2">
|
||||
<FolderOpen className="w-4 h-4 text-muted-foreground" />
|
||||
<span className="truncate">{name}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Servers List */}
|
||||
{selectedProjectPath && (
|
||||
<div className="space-y-2">
|
||||
{isFetchingServers ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="animate-spin text-muted-foreground">-</div>
|
||||
<span className="ml-2 text-sm text-muted-foreground">
|
||||
{formatMessage({ id: 'common.actions.loading' })}
|
||||
</span>
|
||||
</div>
|
||||
) : otherServers.length === 0 ? (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground border border-dashed rounded-lg">
|
||||
{formatMessage(
|
||||
{ id: 'mcp.otherProjects.noServers' },
|
||||
{ project: selectedProjectName }
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2 max-h-60 overflow-y-auto">
|
||||
{otherServers.map((server) => (
|
||||
<div
|
||||
key={`${server.projectPath}-${server.name}`}
|
||||
className="flex items-center gap-3 p-3 rounded-lg bg-muted/30 hover:bg-muted/50 transition-colors"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-medium text-foreground truncate">
|
||||
{server.name}
|
||||
</span>
|
||||
{server.enabled && (
|
||||
<Badge variant="success" className="text-xs">
|
||||
{formatMessage({ id: 'mcp.status.enabled' })}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-mono truncate">
|
||||
{server.command} {(server.args || []).join(' ')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
<span className="font-medium">{server.projectName}</span>
|
||||
<ChevronRight className="inline w-3 h-3 mx-1" />
|
||||
<span className="font-mono">{server.projectPath}</span>
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handleImportServer(server)}
|
||||
disabled={isCreating}
|
||||
className="flex-shrink-0"
|
||||
>
|
||||
<Copy className="w-4 h-4 mr-1" />
|
||||
{formatMessage({ id: 'mcp.otherProjects.import' })}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Hint */}
|
||||
{selectedProjectPath && otherServers.length > 0 && (
|
||||
<p className="text-xs text-muted-foreground mt-3">
|
||||
{formatMessage({ id: 'mcp.otherProjects.hint' })}
|
||||
</p>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default OtherProjectsSection;
|
||||
346
ccw/frontend/src/components/mcp/RecommendedMcpSection.tsx
Normal file
346
ccw/frontend/src/components/mcp/RecommendedMcpSection.tsx
Normal file
@@ -0,0 +1,346 @@
|
||||
// ========================================
|
||||
// Recommended MCP Section Component
|
||||
// ========================================
|
||||
// Display recommended MCP servers with one-click install functionality
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
Search,
|
||||
Globe,
|
||||
Sparkles,
|
||||
Download,
|
||||
Check,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogFooter,
|
||||
} from '@/components/ui/Dialog';
|
||||
import {
|
||||
createMcpServer,
|
||||
fetchMcpServers,
|
||||
} from '@/lib/api';
|
||||
import { mcpServersKeys } from '@/hooks';
|
||||
import { useNotifications } from '@/hooks/useNotifications';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
/**
|
||||
* Recommended server configuration
|
||||
*/
|
||||
export interface RecommendedServer {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
command: string;
|
||||
args: string[];
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
category: 'search' | 'browser' | 'ai';
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for RecommendedMcpSection component
|
||||
*/
|
||||
export interface RecommendedMcpSectionProps {
|
||||
/** Callback when server is installed */
|
||||
onInstallComplete?: () => void;
|
||||
}
|
||||
|
||||
interface RecommendedServerCardProps {
|
||||
server: RecommendedServer;
|
||||
isInstalled: boolean;
|
||||
isInstalling: boolean;
|
||||
onInstall: (server: RecommendedServer) => void;
|
||||
}
|
||||
|
||||
// ========== Constants ==========
|
||||
|
||||
/**
|
||||
* Pre-configured recommended MCP servers
|
||||
*/
|
||||
const RECOMMENDED_SERVERS: RecommendedServer[] = [
|
||||
{
|
||||
id: 'ace-tool',
|
||||
name: 'ACE Tool',
|
||||
description: 'Advanced code search and context engine for intelligent code discovery',
|
||||
command: 'mcp__ace-tool__search_context',
|
||||
args: [],
|
||||
icon: Search,
|
||||
category: 'search',
|
||||
},
|
||||
{
|
||||
id: 'chrome-devtools',
|
||||
name: 'Chrome DevTools',
|
||||
description: 'Browser automation and debugging tools for web development',
|
||||
command: 'mcp__chrome-devtools',
|
||||
args: [],
|
||||
icon: Globe,
|
||||
category: 'browser',
|
||||
},
|
||||
{
|
||||
id: 'exa-search',
|
||||
name: 'Exa Search',
|
||||
description: 'AI-powered web search with real-time crawling capabilities',
|
||||
command: 'mcp__exa__search',
|
||||
args: [],
|
||||
icon: Sparkles,
|
||||
category: 'ai',
|
||||
},
|
||||
];
|
||||
|
||||
// ========== Helper Component ==========
|
||||
|
||||
/**
|
||||
* Individual recommended server card
|
||||
*/
|
||||
function RecommendedServerCard({
|
||||
server,
|
||||
isInstalled,
|
||||
isInstalling,
|
||||
onInstall,
|
||||
}: RecommendedServerCardProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const Icon = server.icon;
|
||||
|
||||
return (
|
||||
<Card className="p-4 hover:shadow-md transition-shadow">
|
||||
<div className="flex items-start gap-3">
|
||||
{/* Icon */}
|
||||
<div className={cn(
|
||||
'p-2.5 rounded-lg',
|
||||
isInstalled ? 'bg-primary/20' : 'bg-muted'
|
||||
)}>
|
||||
<Icon className={cn(
|
||||
'w-5 h-5',
|
||||
isInstalled ? 'text-primary' : 'text-muted-foreground'
|
||||
)} />
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<h4 className="text-sm font-medium text-foreground truncate">
|
||||
{server.name}
|
||||
</h4>
|
||||
{isInstalled && (
|
||||
<Badge variant="default" className="text-xs">
|
||||
{formatMessage({ id: 'mcp.recommended.actions.installed' })}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground line-clamp-2 mb-3">
|
||||
{server.description}
|
||||
</p>
|
||||
|
||||
{/* Install Button */}
|
||||
{!isInstalled && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onInstall(server)}
|
||||
disabled={isInstalling}
|
||||
className="w-full"
|
||||
>
|
||||
{isInstalling ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-1 animate-spin" />
|
||||
{formatMessage({ id: 'mcp.recommended.actions.installing' })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="w-4 h-4 mr-1" />
|
||||
{formatMessage({ id: 'mcp.recommended.actions.install' })}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Installed Indicator */}
|
||||
{isInstalled && (
|
||||
<div className="flex items-center gap-1 text-xs text-primary">
|
||||
<Check className="w-4 h-4" />
|
||||
<span>{formatMessage({ id: 'mcp.recommended.actions.installed' })}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
// ========== Main Component ==========
|
||||
|
||||
/**
|
||||
* Recommended MCP servers section with one-click install
|
||||
*/
|
||||
export function RecommendedMcpSection({
|
||||
onInstallComplete,
|
||||
}: RecommendedMcpSectionProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const queryClient = useQueryClient();
|
||||
const { success, error } = useNotifications();
|
||||
|
||||
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
|
||||
const [selectedServer, setSelectedServer] = useState<RecommendedServer | null>(null);
|
||||
const [installingServerId, setInstallingServerId] = useState<string | null>(null);
|
||||
const [installedServerIds, setInstalledServerIds] = useState<Set<string>>(new Set());
|
||||
|
||||
// Check which servers are already installed
|
||||
const checkInstalledServers = async () => {
|
||||
try {
|
||||
const data = await fetchMcpServers();
|
||||
const allServers = [...data.project, ...data.global];
|
||||
const installedIds = new Set(
|
||||
allServers
|
||||
.filter(s => s.command.startsWith('mcp__'))
|
||||
.map(s => s.command)
|
||||
);
|
||||
setInstalledServerIds(installedIds);
|
||||
} catch {
|
||||
// Ignore errors during check
|
||||
}
|
||||
};
|
||||
|
||||
// Check on mount
|
||||
useState(() => {
|
||||
checkInstalledServers();
|
||||
});
|
||||
|
||||
// Create server mutation
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (server: Omit<RecommendedServer, 'id' | 'icon' | 'category'>) =>
|
||||
createMcpServer({
|
||||
command: server.command,
|
||||
args: server.args,
|
||||
scope: 'global',
|
||||
enabled: true,
|
||||
}),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: mcpServersKeys.all });
|
||||
setInstalledServerIds(prev => new Set(prev).add(variables.command));
|
||||
setInstallingServerId(null);
|
||||
setConfirmDialogOpen(false);
|
||||
setSelectedServer(null);
|
||||
success(
|
||||
formatMessage({ id: 'mcp.recommended.actions.installed' }),
|
||||
formatMessage({ id: 'mcp.recommended.servers.' + selectedServer?.id + '.name' })
|
||||
);
|
||||
onInstallComplete?.();
|
||||
},
|
||||
onError: () => {
|
||||
setInstallingServerId(null);
|
||||
error(
|
||||
formatMessage({ id: 'mcp.dialog.validation.nameRequired' }),
|
||||
formatMessage({ id: 'mcp.dialog.validation.commandRequired' })
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// Handle install click
|
||||
const handleInstallClick = (server: RecommendedServer) => {
|
||||
setSelectedServer(server);
|
||||
setConfirmDialogOpen(true);
|
||||
};
|
||||
|
||||
// Handle confirm install
|
||||
const handleConfirmInstall = () => {
|
||||
if (!selectedServer) return;
|
||||
setInstallingServerId(selectedServer.id);
|
||||
setConfirmDialogOpen(false);
|
||||
createMutation.mutate(selectedServer);
|
||||
};
|
||||
|
||||
// Check if server is installed
|
||||
const isServerInstalled = (server: RecommendedServer) => {
|
||||
return installedServerIds.has(server.command);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="space-y-4">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">
|
||||
{formatMessage({ id: 'mcp.recommended.title' })}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{formatMessage({ id: 'mcp.recommended.description' })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Server Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
{RECOMMENDED_SERVERS.map((server) => (
|
||||
<RecommendedServerCard
|
||||
key={server.id}
|
||||
server={server}
|
||||
isInstalled={isServerInstalled(server)}
|
||||
isInstalling={installingServerId === server.id}
|
||||
onInstall={handleInstallClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Confirmation Dialog */}
|
||||
<Dialog open={confirmDialogOpen} onOpenChange={setConfirmDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{formatMessage({ id: 'mcp.recommended.actions.install' })} {selectedServer?.name}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{formatMessage(
|
||||
{ id: 'mcp.recommended.description' },
|
||||
{ server: selectedServer?.name }
|
||||
)}
|
||||
</p>
|
||||
<div className="mt-4 p-3 bg-muted rounded-lg">
|
||||
<code className="text-xs font-mono">
|
||||
{selectedServer?.command} {selectedServer?.args.join(' ')}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setConfirmDialogOpen(false)}
|
||||
disabled={createMutation.isPending}
|
||||
>
|
||||
{formatMessage({ id: 'mcp.dialog.actions.cancel' })}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleConfirmInstall}
|
||||
disabled={createMutation.isPending}
|
||||
>
|
||||
{createMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-1 animate-spin" />
|
||||
{formatMessage({ id: 'mcp.recommended.actions.installing' })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="w-4 h-4 mr-1" />
|
||||
{formatMessage({ id: 'mcp.recommended.actions.install' })}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default RecommendedMcpSection;
|
||||
272
ccw/frontend/src/components/mcp/WindowsCompatibilityWarning.tsx
Normal file
272
ccw/frontend/src/components/mcp/WindowsCompatibilityWarning.tsx
Normal file
@@ -0,0 +1,272 @@
|
||||
// ========================================
|
||||
// Windows Compatibility Warning Component
|
||||
// ========================================
|
||||
// Windows-specific warning banner with command detection and auto-fix functionality
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { AlertTriangle, Download, CheckCircle, XCircle, Loader2 } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
export interface WindowsCompatibilityWarningProps {
|
||||
/** Optional: Project path to check commands against */
|
||||
projectPath?: string;
|
||||
/** Optional: Callback when compatibility check completes */
|
||||
onComplete?: (result: CompatibilityCheckResult) => void;
|
||||
}
|
||||
|
||||
export interface CommandDetectionResult {
|
||||
command: string;
|
||||
available: boolean;
|
||||
installUrl?: string;
|
||||
}
|
||||
|
||||
export interface CompatibilityCheckResult {
|
||||
isWindows: boolean;
|
||||
missingCommands: string[];
|
||||
commands: CommandDetectionResult[];
|
||||
canAutoFix: boolean;
|
||||
}
|
||||
|
||||
// ========== Constants ==========
|
||||
|
||||
const COMMON_COMMANDS = [
|
||||
{ name: 'npm', installUrl: 'https://docs.npmjs.com/downloading-and-installing-node-js-and-npm' },
|
||||
{ name: 'node', installUrl: 'https://nodejs.org/' },
|
||||
{ name: 'python', installUrl: 'https://www.python.org/downloads/' },
|
||||
{ name: 'npx', installUrl: 'https://docs.npmjs.com/downloading-and-installing-node-js-and-npm' },
|
||||
];
|
||||
|
||||
// Helper function to check if a command can be auto-fixed
|
||||
function canAutoFixCommand(command: string): boolean {
|
||||
return COMMON_COMMANDS.some((cmd) => cmd.name === command);
|
||||
}
|
||||
|
||||
// ========== API Functions ==========
|
||||
|
||||
async function detectWindowsCommands(projectPath?: string): Promise<CommandDetectionResult[]> {
|
||||
const response = await fetch('/api/mcp/detect-commands', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ projectPath }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to detect commands');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function applyWindowsAutoFix(projectPath?: string): Promise<{ success: boolean; message: string }> {
|
||||
const response = await fetch('/api/mcp/apply-windows-fix', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ projectPath }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to apply Windows fix');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// ========== Sub-Components ==========
|
||||
|
||||
interface CommandDetectionListProps {
|
||||
commands: CommandDetectionResult[];
|
||||
}
|
||||
|
||||
function CommandDetectionList({ commands }: CommandDetectionListProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<div className="space-y-2 mt-3">
|
||||
{commands.map((cmd) => (
|
||||
<div
|
||||
key={cmd.command}
|
||||
className={cn(
|
||||
'flex items-center gap-2 text-sm p-2 rounded-md',
|
||||
cmd.available ? 'bg-success/10' : 'bg-destructive/10'
|
||||
)}
|
||||
>
|
||||
{cmd.available ? (
|
||||
<CheckCircle className="w-4 h-4 text-success" />
|
||||
) : (
|
||||
<XCircle className="w-4 h-4 text-destructive" />
|
||||
)}
|
||||
<code className="font-mono flex-1">{cmd.command}</code>
|
||||
{!cmd.available && cmd.installUrl && (
|
||||
<a
|
||||
href={cmd.installUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-primary hover:underline flex items-center gap-1"
|
||||
>
|
||||
<Download className="w-3 h-3" />
|
||||
{formatMessage({ id: 'mcp.windows.install' })}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ========== Main Component ==========
|
||||
|
||||
export function WindowsCompatibilityWarning({
|
||||
projectPath,
|
||||
onComplete,
|
||||
}: WindowsCompatibilityWarningProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [checkResult, setCheckResult] = useState<CompatibilityCheckResult | null>(null);
|
||||
const [isChecking, setIsChecking] = useState(false);
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
|
||||
// Detect Windows platform
|
||||
const isWindows = typeof window !== 'undefined' && window.navigator.platform.toLowerCase().includes('win');
|
||||
|
||||
// Mutation for auto-fix
|
||||
const autoFixMutation = useMutation({
|
||||
mutationFn: () => applyWindowsAutoFix(projectPath),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mcpServers'] });
|
||||
if (onComplete && checkResult) {
|
||||
onComplete({ ...checkResult, canAutoFix: false });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Check compatibility on mount (Windows only)
|
||||
useEffect(() => {
|
||||
if (!isWindows || dismissed) return;
|
||||
|
||||
const checkCompatibility = async () => {
|
||||
setIsChecking(true);
|
||||
try {
|
||||
const results = await detectWindowsCommands(projectPath);
|
||||
const missingCommands = results.filter((r) => !r.available).map((r) => r.command);
|
||||
const canAutoFix = missingCommands.length > 0 && missingCommands.every(canAutoFixCommand);
|
||||
|
||||
const result: CompatibilityCheckResult = {
|
||||
isWindows: true,
|
||||
missingCommands,
|
||||
commands: results,
|
||||
canAutoFix,
|
||||
};
|
||||
|
||||
setCheckResult(result);
|
||||
if (onComplete) {
|
||||
onComplete(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check Windows compatibility:', error);
|
||||
} finally {
|
||||
setIsChecking(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkCompatibility();
|
||||
}, [isWindows, dismissed, projectPath, onComplete]);
|
||||
|
||||
// Don't render if not Windows, dismissed, or still checking without errors
|
||||
if (!isWindows || dismissed || (isChecking && !checkResult)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Don't show warning if all commands are available
|
||||
if (checkResult && checkResult.missingCommands.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleAutoFix = async () => {
|
||||
autoFixMutation.mutate();
|
||||
};
|
||||
|
||||
const handleDismiss = () => {
|
||||
setDismissed(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="border border-warning/50 bg-warning/5 rounded-lg p-4 mb-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="w-5 h-5 text-warning flex-shrink-0 mt-0.5" />
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-foreground">
|
||||
{formatMessage({ id: 'mcp.windows.title' })}
|
||||
</h4>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
{formatMessage({ id: 'mcp.windows.description' })}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0"
|
||||
onClick={handleDismiss}
|
||||
>
|
||||
×
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{checkResult && (
|
||||
<>
|
||||
{checkResult.missingCommands.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="warning" className="text-xs">
|
||||
{formatMessage({ id: 'mcp.windows.missingCount' }, { count: checkResult.missingCommands.length })}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<CommandDetectionList commands={checkResult.commands} />
|
||||
|
||||
{checkResult.canAutoFix && (
|
||||
<div className="flex items-center gap-2 pt-2 border-t border-warning/20">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleAutoFix}
|
||||
disabled={autoFixMutation.isPending}
|
||||
className="text-xs"
|
||||
>
|
||||
{autoFixMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="w-3 h-3 mr-1 animate-spin" />
|
||||
{formatMessage({ id: 'mcp.windows.fixing' })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle className="w-3 h-3 mr-1" />
|
||||
{formatMessage({ id: 'mcp.windows.autoFix' })}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{formatMessage({ id: 'mcp.windows.autoFixHint' })}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{isChecking && (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
{formatMessage({ id: 'mcp.windows.checking' })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default WindowsCompatibilityWarning;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user