@iaforged/context-code 2.3.3 → 2.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/context-bootstrap.js +7 -5
- package/dist/src/commands/login/login.js +1 -1
- package/dist/src/components/BaseTextInput.js +1 -1
- package/dist/src/components/LogoV2/AnimatedClawd.js +1 -1
- package/dist/src/components/LogoV2/Clawd.js +1 -1
- package/dist/src/components/LogoV2/LogoV2.js +1 -1
- package/dist/src/components/LogoV2/WelcomeV2.js +1 -1
- package/dist/src/components/PromptInput/PromptInputFooterLeftSide.js +1 -1
- package/dist/src/components/SessionTokenFooter.js +1 -1
- package/dist/src/components/Spinner.js +1 -1
- package/dist/src/components/Stats.js +1 -1
- package/dist/src/components/TeleportProgress.js +1 -1
- package/dist/src/components/TextInput.js +1 -1
- package/dist/src/components/design-system/ThemeProvider.js +1 -1
- package/dist/src/components/permissions/AskUserQuestionPermissionRequest/AskUserQuestionPermissionRequest.js +1 -1
- package/dist/src/main.js +1 -1
- package/dist/src/query/stopHooks.js +1 -1
- package/dist/src/screens/REPL.js +1 -1
- package/dist/src/services/PromptSuggestion/promptSuggestion.js +1 -1
- package/dist/src/services/analytics/config.js +1 -1
- package/dist/src/services/analytics/datadog.js +1 -1
- package/dist/src/services/mcp/config.js +1 -1
- package/dist/src/services/tips/tipRegistry.js +1 -1
- package/dist/src/services/toolUseSummary/toolUseSummaryGenerator.js +1 -1
- package/dist/src/tools/BriefTool/UI.js +1 -1
- package/dist/src/utils/computerControlMcp/mcpServer.js +1 -1
- package/dist/src/utils/computerControlMcp/server/.gitattributes +18 -0
- package/dist/src/utils/computerControlMcp/server/Dockerfile +25 -0
- package/dist/src/utils/computerControlMcp/server/LICENSE +21 -0
- package/dist/src/utils/computerControlMcp/server/MANIFEST.in +10 -0
- package/dist/src/utils/computerControlMcp/server/README.md +193 -0
- package/dist/src/utils/computerControlMcp/server/demonstration.gif +0 -0
- package/dist/src/utils/computerControlMcp/server/icon.png +0 -0
- package/dist/src/utils/computerControlMcp/server/pyproject.toml +52 -0
- package/dist/src/utils/computerControlMcp/server/smithery.yaml +13 -0
- package/dist/src/utils/computerControlMcp/server/src/README.md +12 -0
- package/dist/src/utils/computerControlMcp/server/src/computer_control_mcp/FZYTK.TTF +0 -0
- package/dist/src/utils/computerControlMcp/server/src/computer_control_mcp/__init__.py +11 -0
- package/dist/src/utils/computerControlMcp/server/src/computer_control_mcp/__main__.py +21 -0
- package/dist/src/utils/computerControlMcp/server/src/computer_control_mcp/cli.py +128 -0
- package/dist/src/utils/computerControlMcp/server/src/computer_control_mcp/core.py +1008 -0
- package/dist/src/utils/computerControlMcp/server/src/computer_control_mcp/gui.py +126 -0
- package/dist/src/utils/computerControlMcp/server/src/computer_control_mcp/server.py +15 -0
- package/dist/src/utils/computerControlMcp/server/src/computer_control_mcp/test.py +346 -0
- package/dist/src/utils/computerControlMcp/server/src/computer_control_mcp/test_image.png +0 -0
- package/dist/src/utils/computerControlMcp/server/tests/README.md +22 -0
- package/dist/src/utils/computerControlMcp/server/tests/conftest.py +10 -0
- package/dist/src/utils/computerControlMcp/server/tests/rapidocr_test.py +21 -0
- package/dist/src/utils/computerControlMcp/server/tests/run_cli.py +9 -0
- package/dist/src/utils/computerControlMcp/server/tests/run_server.py +15 -0
- package/dist/src/utils/computerControlMcp/server/tests/setup.py +16 -0
- package/dist/src/utils/computerControlMcp/server/tests/test_computer_control.py +161 -0
- package/dist/src/utils/computerControlMcp/server/tests/test_screenshot.py +14 -0
- package/dist/src/utils/computerControlMcp/server/tests/test_wgc_env_var.py +42 -0
- package/dist/src/utils/computerControlMcp/server/tests/test_wgc_screenshot.py +67 -0
- package/dist/src/utils/computerControlMcp/server/uv.lock +4986 -0
- package/dist/src/utils/computerControlMcp/setup.js +1 -1
- package/dist/src/utils/logoV2Utils.js +1 -1
- package/dist/src/utils/model/configs.js +1 -1
- package/dist/src/utils/model/model.js +1 -1
- package/dist/src/utils/model/modelOptions.js +1 -1
- package/dist/src/utils/model/providerModels.js +1 -1
- package/dist/src/utils/sembleMcp/setup.js +1 -1
- package/dist/src/utils/theme.js +1 -1
- package/dist/src/utils/themes/bootstrap.js +1 -1
- package/dist/src/utils/themes/opencodeMapper.js +1 -1
- package/dist/webapp/chunk-VAB2VXFI.js +1 -1
- package/dist/webapp/ngsw.json +1 -1
- package/dist/webapp/polyfills-7R4CRVNH.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{feature as e}from"../../recovery/bunBundleShim.js";import{createRequire as r}from"module";const s=r(import.meta.url);import{chmod as o,open as t,rename as n,stat as i,unlink as c}from"fs/promises";import a from"lodash-es/mapValues.js";import p from"lodash-es/memoize.js";import{dirname as l,join as f,parse as u}from"path";import{getPlatform as d}from"../../utils/platform.js";import{getPluginErrorMessage as m}from"../../types/plugin.js";import{isClaudeInChromeMCPServer as g}from"../../utils/claudeInChrome/common.js";import{getCurrentProjectConfig as v,getGlobalConfig as M,saveCurrentProjectConfig as S,saveGlobalConfig as h}from"../../utils/config.js";import{getCwd as w}from"../../utils/cwd.js";import{logForDebugging as C}from"../../utils/debug.js";import{getErrnoCode as y}from"../../utils/errors.js";import{getFsImplementation as j}from"../../utils/fsOperations.js";import{safeParseJSON as P}from"../../utils/json.js";import{logError as b}from"../../utils/log.js";import{getPluginMcpServers as x}from"../../utils/plugins/mcpPluginIntegration.js";import{loadAllPluginsCacheOnly as $}from"../../utils/plugins/pluginLoader.js";import{isSettingSourceEnabled as E}from"../../utils/settings/constants.js";import{getManagedFilePath as O}from"../../utils/settings/managedPath.js";import{isRestrictedToPluginOnly as F}from"../../utils/settings/pluginOnlyPolicy.js";import{getInitialSettings as A,getSettingsForSource as B}from"../../utils/settings/settings.js";import{isMcpServerCommandEntry as N,isMcpServerNameEntry as D,isMcpServerUrlEntry as k}from"../../utils/settings/types.js";import{jsonStringify as V}from"../../utils/slowOperations.js";import{logEvent as U}from"../analytics/index.js";import{fetchClaudeAIMcpConfigsIfEligible as _}from"./claudeai.js";import{expandEnvVarsInString as T}from"./envExpansion.js";import{McpJsonConfigSchema as W,McpServerConfigSchema as I}from"./types.js";import{getProjectMcpServerStatus as R}from"./utils.js";export function getEnterpriseMcpFilePath(){return f(O(),"managed-mcp.json")}function addScopeToServers(e,r){if(!e)return{};const s={};for(const[o,t]of Object.entries(e))s[o]={...t,scope:r};return s}async function writeMcpjsonFile(e){const r=f(w(),".mcp.json");let s;try{s=(await i(r)).mode}catch(e){if("ENOENT"!==y(e))throw e}const a=`${r}.tmp.${process.pid}.${Date.now()}`,p=await t(a,"w",s??420);try{await p.writeFile(V(e,null,2),{encoding:"utf8"}),await p.datasync()}finally{await p.close()}try{void 0!==s&&await o(a,s),await n(a,r)}catch(e){try{await c(a)}catch{}throw e}}function getServerCommandArray(e){if(void 0!==e.type&&"stdio"!==e.type)return null;const r=e;return[r.command,...r.args??[]]}function commandArraysMatch(e,r){return e.length===r.length&&e.every((e,s)=>e===r[s])}function getServerUrl(e){return"url"in e?e.url:null}const J=["/v2/session_ingress/shttp/mcp/","/v2/ccr-sessions/"];export function unwrapCcrProxyUrl(e){if(!J.some(r=>e.includes(r)))return e;try{const r=new URL(e);return r.searchParams.get("mcp_url")||e}catch{return e}}export function getMcpServerSignature(e){const r=getServerCommandArray(e);if(r)return`stdio:${V(r)}`;const s=getServerUrl(e);return s?`url:${unwrapCcrProxyUrl(s)}`:null}export function dedupPluginMcpServers(e,r){const s=new Map;for(const[e,o]of Object.entries(r)){const r=getMcpServerSignature(o);r&&!s.has(r)&&s.set(r,e)}const o={},t=[],n=new Map;for(const[r,i]of Object.entries(e)){const e=getMcpServerSignature(i);if(null===e){o[r]=i;continue}const c=s.get(e);if(void 0!==c){C(`Suppressing plugin MCP server "${r}": duplicates manually-configured "${c}"`),t.push({name:r,duplicateOf:c});continue}const a=n.get(e);void 0===a?(n.set(e,r),o[r]=i):(C(`Suppressing plugin MCP server "${r}": duplicates earlier plugin server "${a}"`),t.push({name:r,duplicateOf:a}))}return{servers:o,suppressed:t}}export function dedupClaudeAiMcpServers(e,r){const s=new Map;for(const[e,o]of Object.entries(r)){if(isMcpServerDisabled(e))continue;const r=getMcpServerSignature(o);r&&!s.has(r)&&s.set(r,e)}const o={},t=[];for(const[r,n]of Object.entries(e)){const e=getMcpServerSignature(n),i=null!==e?s.get(e):void 0;void 0===i?o[r]=n:(C(`Suppressing claude.ai connector "${r}": duplicates manually-configured "${i}"`),t.push({name:r,duplicateOf:i}))}return{servers:o,suppressed:t}}function urlMatchesPattern(e,r){const s=function(e){const r=e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${r}$`)}(r);return s.test(e)}function isMcpServerDenied(e,r){const s=A();if(!s.deniedMcpServers)return!1;for(const r of s.deniedMcpServers)if(D(r)&&r.serverName===e)return!0;if(r){const e=getServerCommandArray(r);if(e)for(const r of s.deniedMcpServers)if(N(r)&&commandArraysMatch(r.serverCommand,e))return!0;const o=getServerUrl(r);if(o)for(const e of s.deniedMcpServers)if(k(e)&&urlMatchesPattern(o,e.serverUrl))return!0}return!1}function isMcpServerAllowedByPolicy(e,r){if(isMcpServerDenied(e,r))return!1;const s=shouldAllowManagedMcpServersOnly()?B("policySettings")??{}:A();if(!s.allowedMcpServers)return!0;if(0===s.allowedMcpServers.length)return!1;const o=s.allowedMcpServers.some(N),t=s.allowedMcpServers.some(k);if(r){const n=getServerCommandArray(r),i=getServerUrl(r);if(n){if(o){for(const e of s.allowedMcpServers)if(N(e)&&commandArraysMatch(e.serverCommand,n))return!0;return!1}for(const r of s.allowedMcpServers)if(D(r)&&r.serverName===e)return!0;return!1}if(i){if(t){for(const e of s.allowedMcpServers)if(k(e)&&urlMatchesPattern(i,e.serverUrl))return!0;return!1}for(const r of s.allowedMcpServers)if(D(r)&&r.serverName===e)return!0;return!1}for(const r of s.allowedMcpServers)if(D(r)&&r.serverName===e)return!0;return!1}for(const r of s.allowedMcpServers)if(D(r)&&r.serverName===e)return!0;return!1}export function filterMcpServersByPolicy(e){const r={},s=[];for(const[o,t]of Object.entries(e)){const e=t;"sdk"===e.type||isMcpServerAllowedByPolicy(o,e)?r[o]=t:s.push(o)}return{allowed:r,blocked:s}}function expandEnvVars(e){const r=[];function expandString(e){const{expanded:s,missingVars:o}=T(e);return r.push(...o),s}let s;switch(e.type){case void 0:case"stdio":{const r=e;s={...r,command:expandString(r.command),args:r.args.map(expandString),env:r.env?a(r.env,expandString):void 0};break}case"sse":case"http":case"ws":{const r=e;s={...r,url:expandString(r.url),headers:r.headers?a(r.headers,expandString):void 0};break}case"sse-ide":case"ws-ide":case"sdk":case"claudeai-proxy":s=e}return{expanded:s,missingVars:[...new Set(r)]}}export async function addMcpConfig(r,s,o){if(r.match(/[^a-zA-Z0-9_-]/))throw new Error(`Invalid name ${r}. Names can only contain letters, numbers, hyphens, and underscores.`);if(g(r))throw new Error(`Cannot add MCP server "${r}": this name is reserved.`);if(e("CHICAGO_MCP")){const{isComputerUseMCPServer:e}=await import("../../utils/computerUse/common.js");if(e(r))throw new Error(`Cannot add MCP server "${r}": this name is reserved.`)}const{isDatabaseMCPServer:t}=await import("../../utils/databaseMcp/common.js");if(t(r))throw new Error(`Cannot add MCP server "${r}": this name is reserved.`);if(doesEnterpriseMcpConfigExist())throw new Error("Cannot add MCP server: enterprise MCP configuration is active and has exclusive control over MCP servers");const n=I().safeParse(s);if(!n.success){const e=n.error.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join(", ");throw new Error(`Invalid configuration: ${e}`)}const i=n.data;if(isMcpServerDenied(r,i))throw new Error(`Cannot add MCP server "${r}": server is explicitly blocked by enterprise policy`);if(!isMcpServerAllowedByPolicy(r,i))throw new Error(`Cannot add MCP server "${r}": not allowed by enterprise policy`);switch(o){case"project":{const{servers:e}=getProjectMcpConfigsFromCwd();if(e[r])throw new Error(`MCP server ${r} already exists in .mcp.json`);break}case"user":{const e=M();if(e.mcpServers?.[r])throw new Error(`MCP server ${r} already exists in user config`);break}case"local":{const e=v();if(e.mcpServers?.[r])throw new Error(`MCP server ${r} already exists in local config`);break}case"dynamic":throw new Error("Cannot add MCP server to scope: dynamic");case"enterprise":throw new Error("Cannot add MCP server to scope: enterprise");case"claudeai":throw new Error("Cannot add MCP server to scope: claudeai")}switch(o){case"project":{const{servers:e}=getProjectMcpConfigsFromCwd(),s={};for(const[r,o]of Object.entries(e)){const{scope:e,...t}=o;s[r]=t}s[r]=i;const o={mcpServers:s};try{await writeMcpjsonFile(o)}catch(e){throw new Error(`Failed to write to .mcp.json: ${e}`)}break}case"user":h(e=>({...e,mcpServers:{...e.mcpServers,[r]:i}}));break;case"local":S(e=>({...e,mcpServers:{...e.mcpServers,[r]:i}}));break;default:throw new Error(`Cannot add MCP server to scope: ${o}`)}}export async function removeMcpConfig(e,r){switch(r){case"project":{const{servers:r}=getProjectMcpConfigsFromCwd();if(!r[e])throw new Error(`No MCP server found with name: ${e} in .mcp.json`);const s={};for(const[o,t]of Object.entries(r))if(o!==e){const{scope:e,...r}=t;s[o]=r}const o={mcpServers:s};try{await writeMcpjsonFile(o)}catch(e){throw new Error(`Failed to remove from .mcp.json: ${e}`)}break}case"user":{const r=M();if(!r.mcpServers?.[e])throw new Error(`No user-scoped MCP server found with name: ${e}`);h(r=>{const{[e]:s,...o}=r.mcpServers??{};return{...r,mcpServers:o}});break}case"local":{const r=v();if(!r.mcpServers?.[e])throw new Error(`No project-local MCP server found with name: ${e}`);S(r=>{const{[e]:s,...o}=r.mcpServers??{};return{...r,mcpServers:o}});break}default:throw new Error(`Cannot remove MCP server from scope: ${r}`)}}export function getProjectMcpConfigsFromCwd(){if(!E("projectSettings"))return{servers:{},errors:[]};const e=f(w(),".mcp.json"),{config:r,errors:s}=parseMcpConfigFromFilePath({filePath:e,expandVars:!0,scope:"project"});if(!r){const r=s.filter(e=>!e.message.startsWith("MCP config file not found"));return r.length>0?(C(`MCP config errors for ${e}: ${V(r.map(e=>e.message))}`,{level:"error"}),{servers:{},errors:r}):{servers:{},errors:[]}}return{servers:r.mcpServers?addScopeToServers(r.mcpServers,"project"):{},errors:s||[]}}export function getMcpConfigsByScope(e){const r={project:"projectSettings",user:"userSettings",local:"localSettings"};if(e in r&&!E(r[e]))return{servers:{},errors:[]};switch(e){case"project":{const r={},s=[],o=[];let t=w();for(;t!==u(t).root;)o.push(t),t=l(t);for(const t of o.reverse()){const o=f(t,".mcp.json"),{config:n,errors:i}=parseMcpConfigFromFilePath({filePath:o,expandVars:!0,scope:"project"});if(!n){const e=i.filter(e=>!e.message.startsWith("MCP config file not found"));e.length>0&&(C(`MCP config errors for ${o}: ${V(e.map(e=>e.message))}`,{level:"error"}),s.push(...e));continue}n.mcpServers&&Object.assign(r,addScopeToServers(n.mcpServers,e)),i.length>0&&s.push(...i)}return{servers:r,errors:s}}case"user":{const r=M().mcpServers;if(!r)return{servers:{},errors:[]};const{config:s,errors:o}=parseMcpConfig({configObject:{mcpServers:r},expandVars:!0,scope:"user"});return{servers:addScopeToServers(s?.mcpServers,e),errors:o}}case"local":{const r=v().mcpServers;if(!r)return{servers:{},errors:[]};const{config:s,errors:o}=parseMcpConfig({configObject:{mcpServers:r},expandVars:!0,scope:"local"});return{servers:addScopeToServers(s?.mcpServers,e),errors:o}}case"enterprise":{const r=getEnterpriseMcpFilePath(),{config:s,errors:o}=parseMcpConfigFromFilePath({filePath:r,expandVars:!0,scope:"enterprise"});if(!s){const e=o.filter(e=>!e.message.startsWith("MCP config file not found"));return e.length>0?(C(`Enterprise MCP config errors for ${r}: ${V(e.map(e=>e.message))}`,{level:"error"}),{servers:{},errors:e}):{servers:{},errors:[]}}return{servers:addScopeToServers(s.mcpServers,e),errors:o}}}}export function getMcpConfigByName(e){const{servers:r}=getMcpConfigsByScope("enterprise");if(F("mcp"))return r[e]??null;const{servers:s}=getMcpConfigsByScope("user"),{servers:o}=getMcpConfigsByScope("project"),{servers:t}=getMcpConfigsByScope("local");return r[e]?r[e]:t[e]?t[e]:o[e]?o[e]:s[e]?s[e]:null}export async function getClaudeCodeMcpConfigs(e={},r=Promise.resolve({})){const{servers:s}=getMcpConfigsByScope("enterprise");if(doesEnterpriseMcpConfigExist()){const e={};for(const[r,o]of Object.entries(s))isMcpServerAllowedByPolicy(r,o)&&(e[r]=o);return{servers:e,errors:[]}}const o=F("mcp"),t={servers:{}},{servers:n}=o?t:getMcpConfigsByScope("user"),{servers:i}=o?t:getMcpConfigsByScope("project"),{servers:c}=o?t:getMcpConfigsByScope("local"),a={},p=await $(),l=[];if(p.errors.length>0)for(const e of p.errors)if("mcp-config-invalid"===e.type||"mcpb-download-failed"===e.type||"mcpb-extract-failed"===e.type||"mcpb-invalid-manifest"===e.type){const r=`Plugin MCP loading error - ${e.type}: ${m(e)}`;b(new Error(r))}else{const r=e.type;C(`Plugin not available for MCP: ${e.source} - error type: ${r}`)}const f=await Promise.all(p.enabled.map(e=>x(e,l)));for(const e of f)e&&Object.assign(a,e);if(l.length>0)for(const e of l){const r=`Plugin MCP server error - ${e.type}: ${m(e)}`;b(new Error(r))}const u={};for(const[e,r]of Object.entries(i))"approved"===R(e)&&(u[e]=r);const d=await r,g={};for(const[r,s]of Object.entries({...n,...u,...c,...e,...d}))!isMcpServerDisabled(r)&&isMcpServerAllowedByPolicy(r,s)&&(g[r]=s);const v={},M={};for(const[e,r]of Object.entries(a))isMcpServerDisabled(e)||!isMcpServerAllowedByPolicy(e,r)?M[e]=r:v[e]=r;const{servers:S,suppressed:h}=dedupPluginMcpServers(v,g);Object.assign(S,M);for(const{name:e,duplicateOf:r}of h){const s=e.split(":");"plugin"!==s[0]||s.length<3||l.push({type:"mcp-server-suppressed-duplicate",source:e,plugin:s[1],serverName:s.slice(2).join(":"),duplicateOf:r})}const w=Object.assign({},S,n,u,c),y={};for(const[e,r]of Object.entries(w))isMcpServerAllowedByPolicy(e,r)&&(y[e]=r);return{servers:y,errors:l}}export async function getAllMcpConfigs(){if(doesEnterpriseMcpConfigExist())return getClaudeCodeMcpConfigs();const e=_(),{servers:r,errors:s}=await getClaudeCodeMcpConfigs({},e),{allowed:o}=filterMcpServersByPolicy(await e),{servers:t}=dedupClaudeAiMcpServers(o,r);return{servers:Object.assign({},t,r),errors:s}}export function parseMcpConfig(e){const{configObject:r,expandVars:s,scope:o,filePath:t}=e,n=W().safeParse(r);if(!n.success)return{config:null,errors:n.error.issues.map(e=>({...t&&{file:t},path:e.path.join("."),message:"Does not adhere to MCP server configuration schema",mcpErrorMetadata:{scope:o,severity:"fatal"}}))};const i=[],c={};for(const[e,r]of Object.entries(n.data.mcpServers)){let n=r;if(s){const{expanded:s,missingVars:c}=expandEnvVars(r);c.length>0&&i.push({...t&&{file:t},path:`mcpServers.${e}`,message:`Missing environment variables: ${c.join(", ")}`,suggestion:`Set the following environment variables: ${c.join(", ")}`,mcpErrorMetadata:{scope:o,serverName:e,severity:"warning"}}),n=s}"windows"!==d()||n.type&&"stdio"!==n.type||"npx"!==n.command&&!n.command.endsWith("\\npx")&&!n.command.endsWith("/npx")||i.push({...t&&{file:t},path:`mcpServers.${e}`,message:"Windows requires 'cmd /c' wrapper to execute npx",suggestion:'Change command to "cmd" with args ["/c", "npx", ...]',mcpErrorMetadata:{scope:o,serverName:e,severity:"warning"}}),c[e]=n}return{config:{mcpServers:c},errors:i}}export function parseMcpConfigFromFilePath(e){const{filePath:r,expandVars:s,scope:o}=e,t=j();let n;try{n=t.readFileSync(r,{encoding:"utf8"})}catch(e){return"ENOENT"===y(e)?{config:null,errors:[{file:r,path:"",message:`MCP config file not found: ${r}`,suggestion:"Check that the file path is correct",mcpErrorMetadata:{scope:o,severity:"fatal"}}]}:(C(`MCP config read error for ${r} (scope=${o}): ${e}`,{level:"error"}),{config:null,errors:[{file:r,path:"",message:`Failed to read file: ${e}`,suggestion:"Check file permissions and ensure the file exists",mcpErrorMetadata:{scope:o,severity:"fatal"}}]})}const i=P(n);return i?parseMcpConfig({configObject:i,expandVars:s,scope:o,filePath:r}):(C(`MCP config is not valid JSON: ${r} (scope=${o}, length=${n.length}, first100=${V(n.slice(0,100))})`,{level:"error"}),{config:null,errors:[{file:r,path:"",message:"MCP config is not a valid JSON",suggestion:"Fix the JSON syntax errors in the file",mcpErrorMetadata:{scope:o,severity:"fatal"}}]})}export const doesEnterpriseMcpConfigExist=p(()=>{const{config:e}=parseMcpConfigFromFilePath({filePath:getEnterpriseMcpFilePath(),expandVars:!0,scope:"enterprise"});return null!==e});export function shouldAllowManagedMcpServersOnly(){return!0===B("policySettings")?.allowManagedMcpServersOnly}export function areMcpConfigsAllowedWithEnterpriseMcpConfig(e){return Object.values(e).every(e=>"sdk"===e.type&&"claude-vscode"===e.name)}const z=e("CHICAGO_MCP")?s("../../utils/computerUse/common.js").COMPUTER_USE_MCP_SERVER_NAME:null;function isDefaultDisabledBuiltin(e){return null!==z&&e===z}export function isMcpServerDisabled(e){const r=v();if(isDefaultDisabledBuiltin(e)){return!(r.enabledMcpServers||[]).includes(e)}return(r.disabledMcpServers||[]).includes(e)}function toggleMembership(e,r,s){return e.includes(r)===s?e:s?[...e,r]:e.filter(e=>e!==r)}export function setMcpServerEnabled(e,r){const s=isDefaultDisabledBuiltin(e)&&isMcpServerDisabled(e)===r;S(s=>{if(isDefaultDisabledBuiltin(e)){const o=s.enabledMcpServers||[],t=toggleMembership(o,e,r);return t===o?s:{...s,enabledMcpServers:t}}const o=s.disabledMcpServers||[],t=toggleMembership(o,e,!r);return t===o?s:{...s,disabledMcpServers:t}}),s&&U("tengu_builtin_mcp_toggle",{serverName:e,enabled:r})}
|
|
1
|
+
import{feature as e}from"../../recovery/bunBundleShim.js";import{createRequire as r}from"module";const s=r(import.meta.url);import{chmod as o,open as t,rename as n,stat as i,unlink as c}from"fs/promises";import a from"lodash-es/mapValues.js";import p from"lodash-es/memoize.js";import{dirname as l,join as f,parse as u}from"path";import{getPlatform as d}from"../../utils/platform.js";import{getPluginErrorMessage as m}from"../../types/plugin.js";import{isClaudeInChromeMCPServer as g}from"../../utils/claudeInChrome/common.js";import{getCurrentProjectConfig as v,getGlobalConfig as M,saveCurrentProjectConfig as S,saveGlobalConfig as h}from"../../utils/config.js";import{getCwd as w}from"../../utils/cwd.js";import{logForDebugging as C}from"../../utils/debug.js";import{getErrnoCode as y}from"../../utils/errors.js";import{getFsImplementation as j}from"../../utils/fsOperations.js";import{safeParseJSON as P}from"../../utils/json.js";import{logError as b}from"../../utils/log.js";import{getPluginMcpServers as x}from"../../utils/plugins/mcpPluginIntegration.js";import{loadAllPluginsCacheOnly as $}from"../../utils/plugins/pluginLoader.js";import{isSettingSourceEnabled as E}from"../../utils/settings/constants.js";import{getManagedFilePath as O}from"../../utils/settings/managedPath.js";import{isRestrictedToPluginOnly as F}from"../../utils/settings/pluginOnlyPolicy.js";import{getInitialSettings as A,getSettingsForSource as B}from"../../utils/settings/settings.js";import{isMcpServerCommandEntry as N,isMcpServerNameEntry as D,isMcpServerUrlEntry as k}from"../../utils/settings/types.js";import{jsonStringify as V}from"../../utils/slowOperations.js";import{logEvent as U}from"../analytics/index.js";import{fetchClaudeAIMcpConfigsIfEligible as _}from"./claudeai.js";import{expandEnvVarsInString as T}from"./envExpansion.js";import{McpJsonConfigSchema as W,McpServerConfigSchema as I}from"./types.js";import{getProjectMcpServerStatus as R}from"./utils.js";export function getEnterpriseMcpFilePath(){return f(O(),"managed-mcp.json")}function addScopeToServers(e,r){if(!e)return{};const s={};for(const[o,t]of Object.entries(e))s[o]={...t,scope:r};return s}async function writeMcpjsonFile(e){const r=f(w(),".mcp.json");let s;try{s=(await i(r)).mode}catch(e){if("ENOENT"!==y(e))throw e}const a=`${r}.tmp.${process.pid}.${Date.now()}`,p=await t(a,"w",s??420);try{await p.writeFile(V(e,null,2),{encoding:"utf8"}),await p.datasync()}finally{await p.close()}try{void 0!==s&&await o(a,s),await n(a,r)}catch(e){try{await c(a)}catch{}throw e}}function getServerCommandArray(e){if(void 0!==e.type&&"stdio"!==e.type)return null;const r=e;return[r.command,...r.args??[]]}function commandArraysMatch(e,r){return e.length===r.length&&e.every((e,s)=>e===r[s])}function getServerUrl(e){return"url"in e?e.url:null}const J=["/v2/session_ingress/shttp/mcp/","/v2/ccr-sessions/"];export function unwrapCcrProxyUrl(e){if(!J.some(r=>e.includes(r)))return e;try{const r=new URL(e);return r.searchParams.get("mcp_url")||e}catch{return e}}export function getMcpServerSignature(e){const r=getServerCommandArray(e);if(r)return`stdio:${V(r)}`;const s=getServerUrl(e);return s?`url:${unwrapCcrProxyUrl(s)}`:null}export function dedupPluginMcpServers(e,r){const s=new Map;for(const[e,o]of Object.entries(r)){const r=getMcpServerSignature(o);r&&!s.has(r)&&s.set(r,e)}const o={},t=[],n=new Map;for(const[r,i]of Object.entries(e)){const e=getMcpServerSignature(i);if(null===e){o[r]=i;continue}const c=s.get(e);if(void 0!==c){C(`Suppressing plugin MCP server "${r}": duplicates manually-configured "${c}"`),t.push({name:r,duplicateOf:c});continue}const a=n.get(e);void 0===a?(n.set(e,r),o[r]=i):(C(`Suppressing plugin MCP server "${r}": duplicates earlier plugin server "${a}"`),t.push({name:r,duplicateOf:a}))}return{servers:o,suppressed:t}}export function dedupClaudeAiMcpServers(e,r){const s=new Map;for(const[e,o]of Object.entries(r)){if(isMcpServerDisabled(e))continue;const r=getMcpServerSignature(o);r&&!s.has(r)&&s.set(r,e)}const o={},t=[];for(const[r,n]of Object.entries(e)){const e=getMcpServerSignature(n),i=null!==e?s.get(e):void 0;void 0===i?o[r]=n:(C(`Suppressing claude.ai connector "${r}": duplicates manually-configured "${i}"`),t.push({name:r,duplicateOf:i}))}return{servers:o,suppressed:t}}function urlMatchesPattern(e,r){const s=function(e){const r=e.replace(/[.+?^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp(`^${r}$`)}(r);return s.test(e)}function isMcpServerDenied(e,r){const s=A();if(!s.deniedMcpServers)return!1;for(const r of s.deniedMcpServers)if(D(r)&&r.serverName===e)return!0;if(r){const e=getServerCommandArray(r);if(e)for(const r of s.deniedMcpServers)if(N(r)&&commandArraysMatch(r.serverCommand,e))return!0;const o=getServerUrl(r);if(o)for(const e of s.deniedMcpServers)if(k(e)&&urlMatchesPattern(o,e.serverUrl))return!0}return!1}function isMcpServerAllowedByPolicy(e,r){if(isMcpServerDenied(e,r))return!1;const s=shouldAllowManagedMcpServersOnly()?B("policySettings")??{}:A();if(!s.allowedMcpServers)return!0;if(0===s.allowedMcpServers.length)return!1;const o=s.allowedMcpServers.some(N),t=s.allowedMcpServers.some(k);if(r){const n=getServerCommandArray(r),i=getServerUrl(r);if(n){if(o){for(const e of s.allowedMcpServers)if(N(e)&&commandArraysMatch(e.serverCommand,n))return!0;return!1}for(const r of s.allowedMcpServers)if(D(r)&&r.serverName===e)return!0;return!1}if(i){if(t){for(const e of s.allowedMcpServers)if(k(e)&&urlMatchesPattern(i,e.serverUrl))return!0;return!1}for(const r of s.allowedMcpServers)if(D(r)&&r.serverName===e)return!0;return!1}for(const r of s.allowedMcpServers)if(D(r)&&r.serverName===e)return!0;return!1}for(const r of s.allowedMcpServers)if(D(r)&&r.serverName===e)return!0;return!1}export function filterMcpServersByPolicy(e){const r={},s=[];for(const[o,t]of Object.entries(e)){const e=t;"sdk"===e.type||isMcpServerAllowedByPolicy(o,e)?r[o]=t:s.push(o)}return{allowed:r,blocked:s}}function expandEnvVars(e){const r=[];function expandString(e){const{expanded:s,missingVars:o}=T(e);return r.push(...o),s}let s;switch(e.type){case void 0:case"stdio":{const r=e;s={...r,command:expandString(r.command),args:r.args.map(expandString),env:r.env?a(r.env,expandString):void 0};break}case"sse":case"http":case"ws":{const r=e;s={...r,url:expandString(r.url),headers:r.headers?a(r.headers,expandString):void 0};break}case"sse-ide":case"ws-ide":case"sdk":case"claudeai-proxy":s=e}return{expanded:s,missingVars:[...new Set(r)]}}export async function addMcpConfig(r,s,o){if(r.match(/[^a-zA-Z0-9_-]/))throw new Error(`Invalid name ${r}. Names can only contain letters, numbers, hyphens, and underscores.`);if(g(r))throw new Error(`Cannot add MCP server "${r}": this name is reserved.`);if(e("CHICAGO_MCP")){const{isComputerUseMCPServer:e}=await import("../../utils/computerUse/common.js");if(e(r))throw new Error(`Cannot add MCP server "${r}": this name is reserved.`)}const{isDatabaseMCPServer:t}=await import("../../utils/databaseMcp/common.js");if(t(r))throw new Error(`Cannot add MCP server "${r}": this name is reserved.`);const{isComputerControlMCPServer:n}=await import("../../utils/computerControlMcp/common.js");if(n(r))throw new Error(`Cannot add MCP server "${r}": this name is reserved.`);if(doesEnterpriseMcpConfigExist())throw new Error("Cannot add MCP server: enterprise MCP configuration is active and has exclusive control over MCP servers");const i=I().safeParse(s);if(!i.success){const e=i.error.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join(", ");throw new Error(`Invalid configuration: ${e}`)}const c=i.data;if(isMcpServerDenied(r,c))throw new Error(`Cannot add MCP server "${r}": server is explicitly blocked by enterprise policy`);if(!isMcpServerAllowedByPolicy(r,c))throw new Error(`Cannot add MCP server "${r}": not allowed by enterprise policy`);switch(o){case"project":{const{servers:e}=getProjectMcpConfigsFromCwd();if(e[r])throw new Error(`MCP server ${r} already exists in .mcp.json`);break}case"user":{const e=M();if(e.mcpServers?.[r])throw new Error(`MCP server ${r} already exists in user config`);break}case"local":{const e=v();if(e.mcpServers?.[r])throw new Error(`MCP server ${r} already exists in local config`);break}case"dynamic":throw new Error("Cannot add MCP server to scope: dynamic");case"enterprise":throw new Error("Cannot add MCP server to scope: enterprise");case"claudeai":throw new Error("Cannot add MCP server to scope: claudeai")}switch(o){case"project":{const{servers:e}=getProjectMcpConfigsFromCwd(),s={};for(const[r,o]of Object.entries(e)){const{scope:e,...t}=o;s[r]=t}s[r]=c;const o={mcpServers:s};try{await writeMcpjsonFile(o)}catch(e){throw new Error(`Failed to write to .mcp.json: ${e}`)}break}case"user":h(e=>({...e,mcpServers:{...e.mcpServers,[r]:c}}));break;case"local":S(e=>({...e,mcpServers:{...e.mcpServers,[r]:c}}));break;default:throw new Error(`Cannot add MCP server to scope: ${o}`)}}export async function removeMcpConfig(e,r){switch(r){case"project":{const{servers:r}=getProjectMcpConfigsFromCwd();if(!r[e])throw new Error(`No MCP server found with name: ${e} in .mcp.json`);const s={};for(const[o,t]of Object.entries(r))if(o!==e){const{scope:e,...r}=t;s[o]=r}const o={mcpServers:s};try{await writeMcpjsonFile(o)}catch(e){throw new Error(`Failed to remove from .mcp.json: ${e}`)}break}case"user":{const r=M();if(!r.mcpServers?.[e])throw new Error(`No user-scoped MCP server found with name: ${e}`);h(r=>{const{[e]:s,...o}=r.mcpServers??{};return{...r,mcpServers:o}});break}case"local":{const r=v();if(!r.mcpServers?.[e])throw new Error(`No project-local MCP server found with name: ${e}`);S(r=>{const{[e]:s,...o}=r.mcpServers??{};return{...r,mcpServers:o}});break}default:throw new Error(`Cannot remove MCP server from scope: ${r}`)}}export function getProjectMcpConfigsFromCwd(){if(!E("projectSettings"))return{servers:{},errors:[]};const e=f(w(),".mcp.json"),{config:r,errors:s}=parseMcpConfigFromFilePath({filePath:e,expandVars:!0,scope:"project"});if(!r){const r=s.filter(e=>!e.message.startsWith("MCP config file not found"));return r.length>0?(C(`MCP config errors for ${e}: ${V(r.map(e=>e.message))}`,{level:"error"}),{servers:{},errors:r}):{servers:{},errors:[]}}return{servers:r.mcpServers?addScopeToServers(r.mcpServers,"project"):{},errors:s||[]}}export function getMcpConfigsByScope(e){const r={project:"projectSettings",user:"userSettings",local:"localSettings"};if(e in r&&!E(r[e]))return{servers:{},errors:[]};switch(e){case"project":{const r={},s=[],o=[];let t=w();for(;t!==u(t).root;)o.push(t),t=l(t);for(const t of o.reverse()){const o=f(t,".mcp.json"),{config:n,errors:i}=parseMcpConfigFromFilePath({filePath:o,expandVars:!0,scope:"project"});if(!n){const e=i.filter(e=>!e.message.startsWith("MCP config file not found"));e.length>0&&(C(`MCP config errors for ${o}: ${V(e.map(e=>e.message))}`,{level:"error"}),s.push(...e));continue}n.mcpServers&&Object.assign(r,addScopeToServers(n.mcpServers,e)),i.length>0&&s.push(...i)}return{servers:r,errors:s}}case"user":{const r=M().mcpServers;if(!r)return{servers:{},errors:[]};const{config:s,errors:o}=parseMcpConfig({configObject:{mcpServers:r},expandVars:!0,scope:"user"});return{servers:addScopeToServers(s?.mcpServers,e),errors:o}}case"local":{const r=v().mcpServers;if(!r)return{servers:{},errors:[]};const{config:s,errors:o}=parseMcpConfig({configObject:{mcpServers:r},expandVars:!0,scope:"local"});return{servers:addScopeToServers(s?.mcpServers,e),errors:o}}case"enterprise":{const r=getEnterpriseMcpFilePath(),{config:s,errors:o}=parseMcpConfigFromFilePath({filePath:r,expandVars:!0,scope:"enterprise"});if(!s){const e=o.filter(e=>!e.message.startsWith("MCP config file not found"));return e.length>0?(C(`Enterprise MCP config errors for ${r}: ${V(e.map(e=>e.message))}`,{level:"error"}),{servers:{},errors:e}):{servers:{},errors:[]}}return{servers:addScopeToServers(s.mcpServers,e),errors:o}}}}export function getMcpConfigByName(e){const{servers:r}=getMcpConfigsByScope("enterprise");if(F("mcp"))return r[e]??null;const{servers:s}=getMcpConfigsByScope("user"),{servers:o}=getMcpConfigsByScope("project"),{servers:t}=getMcpConfigsByScope("local");return r[e]?r[e]:t[e]?t[e]:o[e]?o[e]:s[e]?s[e]:null}export async function getClaudeCodeMcpConfigs(e={},r=Promise.resolve({})){const{servers:s}=getMcpConfigsByScope("enterprise");if(doesEnterpriseMcpConfigExist()){const e={};for(const[r,o]of Object.entries(s))isMcpServerAllowedByPolicy(r,o)&&(e[r]=o);return{servers:e,errors:[]}}const o=F("mcp"),t={servers:{}},{servers:n}=o?t:getMcpConfigsByScope("user"),{servers:i}=o?t:getMcpConfigsByScope("project"),{servers:c}=o?t:getMcpConfigsByScope("local"),a={},p=await $(),l=[];if(p.errors.length>0)for(const e of p.errors)if("mcp-config-invalid"===e.type||"mcpb-download-failed"===e.type||"mcpb-extract-failed"===e.type||"mcpb-invalid-manifest"===e.type){const r=`Plugin MCP loading error - ${e.type}: ${m(e)}`;b(new Error(r))}else{const r=e.type;C(`Plugin not available for MCP: ${e.source} - error type: ${r}`)}const f=await Promise.all(p.enabled.map(e=>x(e,l)));for(const e of f)e&&Object.assign(a,e);if(l.length>0)for(const e of l){const r=`Plugin MCP server error - ${e.type}: ${m(e)}`;b(new Error(r))}const u={};for(const[e,r]of Object.entries(i))"approved"===R(e)&&(u[e]=r);const d=await r,g={};for(const[r,s]of Object.entries({...n,...u,...c,...e,...d}))!isMcpServerDisabled(r)&&isMcpServerAllowedByPolicy(r,s)&&(g[r]=s);const v={},M={};for(const[e,r]of Object.entries(a))isMcpServerDisabled(e)||!isMcpServerAllowedByPolicy(e,r)?M[e]=r:v[e]=r;const{servers:S,suppressed:h}=dedupPluginMcpServers(v,g);Object.assign(S,M);for(const{name:e,duplicateOf:r}of h){const s=e.split(":");"plugin"!==s[0]||s.length<3||l.push({type:"mcp-server-suppressed-duplicate",source:e,plugin:s[1],serverName:s.slice(2).join(":"),duplicateOf:r})}const w=Object.assign({},S,n,u,c),y={};for(const[e,r]of Object.entries(w))isMcpServerAllowedByPolicy(e,r)&&(y[e]=r);return{servers:y,errors:l}}export async function getAllMcpConfigs(){if(doesEnterpriseMcpConfigExist())return getClaudeCodeMcpConfigs();const e=_(),{servers:r,errors:s}=await getClaudeCodeMcpConfigs({},e),{allowed:o}=filterMcpServersByPolicy(await e),{servers:t}=dedupClaudeAiMcpServers(o,r);return{servers:Object.assign({},t,r),errors:s}}export function parseMcpConfig(e){const{configObject:r,expandVars:s,scope:o,filePath:t}=e,n=W().safeParse(r);if(!n.success)return{config:null,errors:n.error.issues.map(e=>({...t&&{file:t},path:e.path.join("."),message:"Does not adhere to MCP server configuration schema",mcpErrorMetadata:{scope:o,severity:"fatal"}}))};const i=[],c={};for(const[e,r]of Object.entries(n.data.mcpServers)){let n=r;if(s){const{expanded:s,missingVars:c}=expandEnvVars(r);c.length>0&&i.push({...t&&{file:t},path:`mcpServers.${e}`,message:`Missing environment variables: ${c.join(", ")}`,suggestion:`Set the following environment variables: ${c.join(", ")}`,mcpErrorMetadata:{scope:o,serverName:e,severity:"warning"}}),n=s}"windows"!==d()||n.type&&"stdio"!==n.type||"npx"!==n.command&&!n.command.endsWith("\\npx")&&!n.command.endsWith("/npx")||i.push({...t&&{file:t},path:`mcpServers.${e}`,message:"Windows requires 'cmd /c' wrapper to execute npx",suggestion:'Change command to "cmd" with args ["/c", "npx", ...]',mcpErrorMetadata:{scope:o,serverName:e,severity:"warning"}}),c[e]=n}return{config:{mcpServers:c},errors:i}}export function parseMcpConfigFromFilePath(e){const{filePath:r,expandVars:s,scope:o}=e,t=j();let n;try{n=t.readFileSync(r,{encoding:"utf8"})}catch(e){return"ENOENT"===y(e)?{config:null,errors:[{file:r,path:"",message:`MCP config file not found: ${r}`,suggestion:"Check that the file path is correct",mcpErrorMetadata:{scope:o,severity:"fatal"}}]}:(C(`MCP config read error for ${r} (scope=${o}): ${e}`,{level:"error"}),{config:null,errors:[{file:r,path:"",message:`Failed to read file: ${e}`,suggestion:"Check file permissions and ensure the file exists",mcpErrorMetadata:{scope:o,severity:"fatal"}}]})}const i=P(n);return i?parseMcpConfig({configObject:i,expandVars:s,scope:o,filePath:r}):(C(`MCP config is not valid JSON: ${r} (scope=${o}, length=${n.length}, first100=${V(n.slice(0,100))})`,{level:"error"}),{config:null,errors:[{file:r,path:"",message:"MCP config is not a valid JSON",suggestion:"Fix the JSON syntax errors in the file",mcpErrorMetadata:{scope:o,severity:"fatal"}}]})}export const doesEnterpriseMcpConfigExist=p(()=>{const{config:e}=parseMcpConfigFromFilePath({filePath:getEnterpriseMcpFilePath(),expandVars:!0,scope:"enterprise"});return null!==e});export function shouldAllowManagedMcpServersOnly(){return!0===B("policySettings")?.allowManagedMcpServersOnly}export function areMcpConfigsAllowedWithEnterpriseMcpConfig(e){return Object.values(e).every(e=>"sdk"===e.type&&"claude-vscode"===e.name)}const z=e("CHICAGO_MCP")?s("../../utils/computerUse/common.js").COMPUTER_USE_MCP_SERVER_NAME:null;function isDefaultDisabledBuiltin(e){return null!==z&&e===z}export function isMcpServerDisabled(e){const r=v();if(isDefaultDisabledBuiltin(e)){return!(r.enabledMcpServers||[]).includes(e)}return(r.disabledMcpServers||[]).includes(e)}function toggleMembership(e,r,s){return e.includes(r)===s?e:s?[...e,r]:e.filter(e=>e!==r)}export function setMcpServerEnabled(e,r){const s=isDefaultDisabledBuiltin(e)&&isMcpServerDisabled(e)===r;S(s=>{if(isDefaultDisabledBuiltin(e)){const o=s.enabledMcpServers||[],t=toggleMembership(o,e,r);return t===o?s:{...s,enabledMcpServers:t}}const o=s.disabledMcpServers||[],t=toggleMembership(o,e,!r);return t===o?s:{...s,disabledMcpServers:t}}),s&&U("tengu_builtin_mcp_toggle",{serverName:e,enabled:r})}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"chalk";import{logForDebugging as n}from"../../utils/debug.js";import{fileHistoryEnabled as a}from"../../utils/fileHistory.js";import{getInitialSettings as s,getSettings_DEPRECATED as t,getSettingsForSource as o}from"../../utils/settings/settings.js";import{shouldOfferTerminalSetup as i}from"../../commands/terminalSetup/terminalSetup.js";import{getDesktopUpsellConfig as r}from"../../components/DesktopUpsell/DesktopUpsellStartup.js";import{color as l}from"../../components/design-system/color.js";import{shouldShowOverageCreditUpsell as c}from"../../components/LogoV2/OverageCreditUpsell.js";import{getShortcutDisplay as d}from"../../keybindings/shortcutFormat.js";import{isKairosCronEnabled as u}from"../../tools/ScheduleCronTool/prompt.js";import{is1PApiCustomer as p}from"../../utils/auth.js";import{countConcurrentSessions as m}from"../../utils/concurrentSessions.js";import{getGlobalConfig as f}from"../../utils/config.js";import{getEffortEnvOverride as y,modelSupportsEffort as g}from"../../utils/effort.js";import{env as v}from"../../utils/env.js";import{cacheKeys as S}from"../../utils/fileStateCache.js";import{getWorktreeCount as h}from"../../utils/git.js";import{detectRunningIDEsCached as w,getSortedIdeLockfiles as R,isCursorInstalled as b,isSupportedTerminal as C,isSupportedVSCodeTerminal as j,isVSCodeInstalled as E,isWindsurfInstalled as P}from"../../utils/ide.js";import{getMainLoopModel as _,getUserSpecifiedModelSetting as T}from"../../utils/model/model.js";import{getPlatform as U}from"../../utils/platform.js";import{isPluginInstalled as k}from"../../utils/plugins/installedPluginsManager.js";import{loadKnownMarketplacesConfigSafe as x}from"../../utils/plugins/marketplaceManager.js";import{OFFICIAL_MARKETPLACE_NAME as O}from"../../utils/plugins/officialMarketplace.js";import{getCurrentSessionAgentColor as $,isCustomTitleEnabled as M}from"../../utils/sessionStorage.js";import{getFeatureValue_CACHED_MAY_BE_STALE as L}from"../analytics/growthbook.js";import{formatGrantAmount as A,getCachedOverageCreditGrant as D}from"../api/overageCreditGrant.js";import{checkCachedPassesEligibility as I,formatCreditAmount as q,getCachedReferrerReward as z}from"../api/referral.js";import{getSessionsSinceLastShown as H}from"./tipHistory.js";let N;async function isMarketplacePluginRelevant(e,n,a){if(!await async function(){if(void 0!==N)return N;const e=await x();return N=O in e,N}())return!1;if(k(`${e}@${O}`))return!1;const{bashTools:s}=n??{};if(a.cli&&s?.size&&a.cli.some(e=>s.has(e)))return!0;if(a.filePath&&n?.readFileState){if(S(n.readFileState).some(e=>a.filePath.test(e)))return!0}return!1}const Y=[{id:"new-user-warmup",content:async()=>"Comienza con pequeñas funciones o correcciones, pide a Context que proponga un plan y verifica sus ediciones sugeridas",cooldownSessions:3,isRelevant:async()=>f().numStartups<10},{id:"plan-mode-for-complex-tasks",content:async()=>`Usa el Modo Plan para preparar una solicitud compleja antes de realizar cambios. Presiona ${d("chat:cycleMode","Chat","shift+tab")} dos veces para activarlo.`,cooldownSessions:5,isRelevant:async()=>{if("ant"===process.env.USER_TYPE)return!1;const e=f();return(e.lastPlanModeUse?(Date.now()-e.lastPlanModeUse)/864e5:1/0)>7}},{id:"default-permission-mode-config",content:async()=>"Usa /config para cambiar tu modo de permisos predeterminado (incluyendo el Modo Plan)",cooldownSessions:10,isRelevant:async()=>{try{const e=f(),n=t(),a=Boolean(e.lastPlanModeUse),s=Boolean(n?.permissions?.defaultMode);return a&&!s}catch(e){return n(`Failed to check default-permission-mode-config tip relevance: ${e}`,{level:"warn"}),!1}}},{id:"git-worktrees",content:async()=>"Usa git worktrees para ejecutar múltiples sesiones de Context en paralelo.",cooldownSessions:10,isRelevant:async()=>{try{const e=f();return await h()<=1&&e.numStartups>50}catch(e){return!1}}},{id:"color-when-multi-clauding",content:async()=>"¿Ejecutando múltiples sesiones de Context? Usa /color y /rename para distinguirlas de un vistazo.",cooldownSessions:10,isRelevant:async()=>{if($())return!1;return await m()>=2}},{id:"terminal-setup",content:async()=>"Apple_Terminal"===v.terminal?"Ejecuta /terminal-setup para habilitar la integración del terminal, como Option + Enter para nueva línea y más":"Ejecuta /terminal-setup para habilitar la integración del terminal, como Shift + Enter para nueva línea y más",cooldownSessions:10,async isRelevant(){const e=f();return"Apple_Terminal"===v.terminal?!e.optionAsMetaKeyInstalled:!e.shiftEnterKeyBindingInstalled}},{id:"shift-enter",content:async()=>"Apple_Terminal"===v.terminal?"Presiona Option+Enter para enviar un mensaje multilínea":"Presiona Shift+Enter para enviar un mensaje multilínea",cooldownSessions:10,async isRelevant(){const e=f();return Boolean(("Apple_Terminal"===v.terminal?e.optionAsMetaKeyInstalled:e.shiftEnterKeyBindingInstalled)&&e.numStartups>3)}},{id:"shift-enter-setup",content:async()=>"Apple_Terminal"===v.terminal?"Ejecuta /terminal-setup para habilitar Option+Enter para nuevas líneas":"Ejecuta /terminal-setup para habilitar Shift+Enter para nuevas líneas",cooldownSessions:10,async isRelevant(){if(!i())return!1;const e=f();return!("Apple_Terminal"===v.terminal?e.optionAsMetaKeyInstalled:e.shiftEnterKeyBindingInstalled)}},{id:"memory-command",content:async()=>"Usa /memory para ver y gestionar la memoria de Context",cooldownSessions:15,isRelevant:async()=>f().memoryUsageCount<=0},{id:"theme-command",content:async()=>"Usa /theme para cambiar el tema de color",cooldownSessions:20,isRelevant:async()=>!0},{id:"colorterm-truecolor",content:async()=>"Prueba a configurar la variable de entorno COLORTERM=truecolor para obtener colores más vivos",cooldownSessions:30,isRelevant:async()=>!process.env.COLORTERM&&e.level<3},{id:"powershell-tool-env",content:async()=>"Configura CONTEXT_CODE_USE_POWERSHELL_TOOL=1 para habilitar la herramienta PowerShell (vista previa)",cooldownSessions:10,isRelevant:async()=>"windows"===U()&&void 0===process.env.CONTEXT_CODE_USE_POWERSHELL_TOOL&&void 0===process.env.CLAUDE_CODE_USE_POWERSHELL_TOOL},{id:"status-line",content:async()=>"Usa /statusline para configurar una línea de estado personalizada que se mostrará debajo del cuadro de entrada",cooldownSessions:25,isRelevant:async()=>void 0===t().statusLine},{id:"prompt-queue",content:async()=>"Pulsa Enter para poner en cola mensajes adicionales mientras Context está trabajando.",cooldownSessions:5,isRelevant:async()=>f().promptQueueUseCount<=3},{id:"enter-to-steer-in-relatime",content:async()=>"Envía mensajes a Context mientras trabaja para guiarlo en tiempo real",cooldownSessions:20,isRelevant:async()=>!0},{id:"todo-list",content:async()=>"Pide a Context que cree una lista de tareas cuando trabajes en tareas complejas para seguir el progreso",cooldownSessions:20,isRelevant:async()=>!0},{id:"vscode-command-install",content:async()=>`Abre la Paleta de Comandos (Cmd+Shift+P) y ejecuta "Shell Command: Install '${"vscode"===v.terminal?"code":v.terminal}' command in PATH" para habilitar la integración con el IDE`,cooldownSessions:0,async isRelevant(){if(!j())return!1;if("macos"!==U())return!1;switch(v.terminal){case"vscode":return!await E();case"cursor":return!await b();case"windsurf":return!await P();default:return!1}}},{id:"ide-upsell-external-terminal",content:async()=>"Conecta Context a tu IDE · /ide",cooldownSessions:4,async isRelevant(){if(C())return!1;if(0!==(await R()).length)return!1;return(await w()).length>0}},{id:"install-github-app",content:async()=>"Ejecuta /install-github-app para etiquetar a @claude directamente desde tus issues y PRs de Github",cooldownSessions:10,isRelevant:async()=>!f().githubActionSetupCount},{id:"install-slack-app",content:async()=>"Ejecuta /install-slack-app para usar Context en Slack",cooldownSessions:10,isRelevant:async()=>!f().slackAppInstallCount},{id:"permissions",content:async()=>"Usa /permissions para pre-aprobar y pre-denegar herramientas de bash, edición y MCP",cooldownSessions:10,isRelevant:async()=>f().numStartups>10},{id:"drag-and-drop-images",content:async()=>"¿Sabías que puedes arrastrar y soltar archivos de imagen en tu terminal?",cooldownSessions:10,isRelevant:async()=>!v.isSSH()},{id:"paste-images-mac",content:async()=>"Pega imágenes en Context Code usando control+v (¡no cmd+v!)",cooldownSessions:10,isRelevant:async()=>"macos"===U()},{id:"double-esc",content:async()=>"Pulsa dos veces esc para rebobinar la conversación a un punto anterior en el tiempo",cooldownSessions:10,isRelevant:async()=>!a()},{id:"double-esc-code-restore",content:async()=>"Pulsa dos veces esc para rebobinar el código y/o la conversación a un punto anterior en el tiempo",cooldownSessions:10,isRelevant:async()=>a()},{id:"continue",content:async()=>"Ejecuta context --continue o context --resume para reanudar una conversación",cooldownSessions:10,isRelevant:async()=>!0},{id:"rename-conversation",content:async()=>"Nombra tus conversaciones con /rename para encontrarlas fácilmente en /resume más tarde",cooldownSessions:15,isRelevant:async()=>M()&&f().numStartups>10},{id:"custom-commands",content:async()=>"Crea habilidades añadiendo archivos .md a .context/skills/ en tu proyecto o ~/.context/skills/ para habilidades globales",cooldownSessions:15,isRelevant:async()=>f().numStartups>10},{id:"shift-tab",content:async()=>"ant"===process.env.USER_TYPE?`Pulsa ${d("chat:cycleMode","Chat","shift+tab")} para alternar entre el modo predeterminado y el modo automático`:`Pulsa ${d("chat:cycleMode","Chat","shift+tab")} para alternar entre el modo predeterminado, el modo de edición con auto-aceptación y el modo de planificación`,cooldownSessions:10,isRelevant:async()=>!0},{id:"image-paste",content:async()=>`Usa ${d("chat:imagePaste","Chat","ctrl+v")} para pegar imágenes desde tu portapapeles`,cooldownSessions:20,isRelevant:async()=>!0},{id:"custom-agents",content:async()=>"Usa /agents para optimizar tareas específicas. Ej: Arquitecto de Software, Escritor de Código, Revisor de Código",cooldownSessions:15,isRelevant:async()=>f().numStartups>5},{id:"agent-flag",content:async()=>"Usa --agent <nombre_agente> para iniciar directamente una conversación con un subagente",cooldownSessions:15,isRelevant:async()=>f().numStartups>5},{id:"desktop-app",content:async()=>"Ejecuta Context Code local o remotamente usando la aplicación de escritorio: clau.de/desktop",cooldownSessions:15,isRelevant:async()=>"linux"!==U()},{id:"desktop-shortcut",content:async e=>`Continúa tu sesión en Context Code Desktop con ${l("suggestion",e.theme)("/desktop")}`,cooldownSessions:15,isRelevant:async()=>!!r().enable_shortcut_tip&&("darwin"===process.platform||"win32"===process.platform&&"x64"===process.arch)},{id:"web-app",content:async()=>"Ejecuta tareas en la nube mientras sigues programando localmente · clau.de/web",cooldownSessions:15,isRelevant:async()=>!0},{id:"mobile-app",content:async()=>"/mobile para usar Context Code desde la aplicación de Context en tu teléfono",cooldownSessions:15,isRelevant:async()=>!0},{id:"opusplan-mode-reminder",content:async()=>`Tu modelo predeterminado es Opus Plan Mode. Presiona ${d("chat:cycleMode","Chat","shift+tab")} dos veces para activar el Modo Plan y planificar con Context Opus.`,cooldownSessions:2,async isRelevant(){if("ant"===process.env.USER_TYPE)return!1;const e=f(),n="opusplan"===T(),a=e.lastPlanModeUse?(Date.now()-e.lastPlanModeUse)/864e5:1/0;return n&&a>3}},{id:"frontend-design-plugin",content:async e=>`¿Trabajando con HTML/CSS? Instala el plugin frontend-design:\n${l("suggestion",e.theme)(`/plugin install frontend-design@${O}`)}`,cooldownSessions:3,isRelevant:async e=>isMarketplacePluginRelevant("frontend-design",e,{filePath:/\.(html|css|htm)$/i})},{id:"vercel-plugin",content:async e=>`¿Trabajando con Vercel? Instala el plugin vercel:\n${l("suggestion",e.theme)(`/plugin install vercel@${O}`)}`,cooldownSessions:3,isRelevant:async e=>isMarketplacePluginRelevant("vercel",e,{filePath:/(?:^|[/\\])vercel\.json$/i,cli:["vercel"]})},{id:"effort-high-nudge",content:async e=>{const n=l("suggestion",e.theme)("/effort high");return"copy_b"===L("tengu_tide_elm","off")?`Usa ${n} para obtener mejores respuestas directas. Context lo analiza profundamente primero.`:`¿Trabajando en algo difícil? ${n} ofrece mejores respuestas iniciales`},cooldownSessions:3,isRelevant:async()=>{if(!p())return!1;if(!g(_()))return!1;if(void 0!==o("policySettings")?.effortLevel)return!1;if(void 0!==y())return!1;const e=s().effortLevel;return"high"!==e&&"max"!==e&&"off"!==L("tengu_tide_elm","off")}},{id:"subagent-fanout-nudge",content:async e=>{const n=l("suggestion",e.theme);return"copy_b"===L("tengu_tern_alloy","off")?`Para tareas grandes, pide a Context que ${n("use subagentes")}. Trabajan en paralelo y mantienen limpia la conversación principal.`:`Di ${n('"fan out subagents"')} y Context enviará un equipo. Cada uno profundiza para que no se escape nada.`},cooldownSessions:3,isRelevant:async()=>!!p()&&"off"!==L("tengu_tern_alloy","off")},{id:"loop-command-nudge",content:async e=>{const n=l("suggestion",e.theme);return"copy_b"===L("tengu_timber_lark","off")?`Usa ${n("/loop 5m check the deploy")} para ejecutar cualquier prompt de forma programada. Configúralo y olvídate.`:`${n("/loop")} ejecuta cualquier prompt de forma recurrente. Ideal para monitorear despliegues o estados de PR.`},cooldownSessions:3,isRelevant:async()=>!!p()&&(!!u()&&"off"!==L("tengu_timber_lark","off"))},{id:"guest-passes",content:async e=>{const n=l("claude",e.theme),a=z();return a?`Comparte Context Code y gana ${n(q(a))} de uso extra · ${n("/passes")}`:`Tienes pases de invitado gratuitos para compartir · ${n("/passes")}`},cooldownSessions:3,isRelevant:async()=>{if(f().hasVisitedPasses)return!1;const{eligible:e}=I();return e}},{id:"overage-credit",content:async e=>{const n=l("claude",e.theme),a=D(),s=a?A(a):null;return s?`${n(`${s} en uso extra, por nuestra cuenta`)} · aplicaciones de terceros · ${n("/extra-usage")}`:""},cooldownSessions:3,isRelevant:async()=>c()},{id:"feedback-command",content:async()=>"¡Usa /feedback para ayudarnos a mejorar!",cooldownSessions:15,async isRelevant(){if("ant"===process.env.USER_TYPE)return!1;return f().numStartups>5}}],B="ant"===process.env.USER_TYPE?[{id:"important-claudemd",content:async()=>'[ANT-ONLY] Use "IMPORTANT:" prefix for must-follow CLAUDE.md rules',cooldownSessions:30,isRelevant:async()=>!0},{id:"skillify",content:async()=>"[ANT-ONLY] Use /skillify at the end of a workflow to turn it into a reusable skill",cooldownSessions:15,isRelevant:async()=>!0}]:[];export async function getRelevantTips(e){const n=s().spinnerTipsOverride,a=function(){const e=s().spinnerTipsOverride;return e?.tips?.length?e.tips.map((e,n)=>({id:`custom-tip-${n}`,content:async()=>e,cooldownSessions:0,isRelevant:async()=>!0})):[]}();if(n?.excludeDefault&&a.length>0)return a;const t=[...Y,...B],o=await Promise.all(t.map(n=>n.isRelevant(e)));return[...t.filter((e,n)=>o[n]).filter(e=>H(e.id)>=e.cooldownSessions),...a]}
|
|
1
|
+
import e from"chalk";import{logForDebugging as n}from"../../utils/debug.js";import{fileHistoryEnabled as a}from"../../utils/fileHistory.js";import{getInitialSettings as s,getSettings_DEPRECATED as t,getSettingsForSource as o}from"../../utils/settings/settings.js";import{shouldOfferTerminalSetup as i}from"../../commands/terminalSetup/terminalSetup.js";import{getDesktopUpsellConfig as r}from"../../components/DesktopUpsell/DesktopUpsellStartup.js";import{color as l}from"../../components/design-system/color.js";import{shouldShowOverageCreditUpsell as c}from"../../components/LogoV2/OverageCreditUpsell.js";import{getShortcutDisplay as d}from"../../keybindings/shortcutFormat.js";import{isKairosCronEnabled as u}from"../../tools/ScheduleCronTool/prompt.js";import{is1PApiCustomer as p}from"../../utils/auth.js";import{countConcurrentSessions as m}from"../../utils/concurrentSessions.js";import{getGlobalConfig as f}from"../../utils/config.js";import{getEffortEnvOverride as y,modelSupportsEffort as g}from"../../utils/effort.js";import{env as v}from"../../utils/env.js";import{cacheKeys as S}from"../../utils/fileStateCache.js";import{getWorktreeCount as h}from"../../utils/git.js";import{detectRunningIDEsCached as w,getSortedIdeLockfiles as R,isCursorInstalled as b,isSupportedTerminal as C,isSupportedVSCodeTerminal as j,isVSCodeInstalled as E,isWindsurfInstalled as P}from"../../utils/ide.js";import{getMainLoopModel as _,getUserSpecifiedModelSetting as T}from"../../utils/model/model.js";import{getPlatform as x}from"../../utils/platform.js";import{isPluginInstalled as U}from"../../utils/plugins/installedPluginsManager.js";import{loadKnownMarketplacesConfigSafe as k}from"../../utils/plugins/marketplaceManager.js";import{OFFICIAL_MARKETPLACE_NAME as O}from"../../utils/plugins/officialMarketplace.js";import{getCurrentSessionAgentColor as $,isCustomTitleEnabled as M}from"../../utils/sessionStorage.js";import{getFeatureValue_CACHED_MAY_BE_STALE as L}from"../analytics/growthbook.js";import{formatGrantAmount as A,getCachedOverageCreditGrant as D}from"../api/overageCreditGrant.js";import{checkCachedPassesEligibility as I,formatCreditAmount as q,getCachedReferrerReward as z}from"../api/referral.js";import{getSessionsSinceLastShown as H}from"./tipHistory.js";let N;async function isMarketplacePluginRelevant(e,n,a){if(!await async function(){if(void 0!==N)return N;const e=await k();return N=O in e,N}())return!1;if(U(`${e}@${O}`))return!1;const{bashTools:s}=n??{};if(a.cli&&s?.size&&a.cli.some(e=>s.has(e)))return!0;if(a.filePath&&n?.readFileState){if(S(n.readFileState).some(e=>a.filePath.test(e)))return!0}return!1}const Y=[{id:"new-user-warmup",content:async()=>"Comienza con pequeñas funciones o correcciones, pide a Context que proponga un plan y verifica sus ediciones sugeridas",cooldownSessions:3,isRelevant:async()=>f().numStartups<10},{id:"plan-mode-for-complex-tasks",content:async()=>`Usa el Modo Plan para preparar una solicitud compleja antes de realizar cambios. Presiona ${d("chat:cycleMode","Chat","shift+tab")} dos veces para activarlo.`,cooldownSessions:5,isRelevant:async()=>{if("ant"===process.env.USER_TYPE)return!1;const e=f();return(e.lastPlanModeUse?(Date.now()-e.lastPlanModeUse)/864e5:1/0)>7}},{id:"default-permission-mode-config",content:async()=>"Usa /config para cambiar tu modo de permisos predeterminado (incluyendo el Modo Plan)",cooldownSessions:10,isRelevant:async()=>{try{const e=f(),n=t(),a=Boolean(e.lastPlanModeUse),s=Boolean(n?.permissions?.defaultMode);return a&&!s}catch(e){return n(`Failed to check default-permission-mode-config tip relevance: ${e}`,{level:"warn"}),!1}}},{id:"git-worktrees",content:async()=>"Usa git worktrees para ejecutar múltiples sesiones de Context en paralelo.",cooldownSessions:10,isRelevant:async()=>{try{const e=f();return await h()<=1&&e.numStartups>50}catch(e){return!1}}},{id:"color-when-multi-clauding",content:async()=>"¿Ejecutando múltiples sesiones de Context? Usa /color y /rename para distinguirlas de un vistazo.",cooldownSessions:10,isRelevant:async()=>{if($())return!1;return await m()>=2}},{id:"terminal-setup",content:async()=>"Apple_Terminal"===v.terminal?"Ejecuta /terminal-setup para habilitar la integración del terminal, como Option + Enter para nueva línea y más":"Ejecuta /terminal-setup para habilitar la integración del terminal, como Shift + Enter para nueva línea y más",cooldownSessions:10,async isRelevant(){const e=f();return"Apple_Terminal"===v.terminal?!e.optionAsMetaKeyInstalled:!e.shiftEnterKeyBindingInstalled}},{id:"shift-enter",content:async()=>"Apple_Terminal"===v.terminal?"Presiona Option+Enter para enviar un mensaje multilínea":"Presiona Shift+Enter para enviar un mensaje multilínea",cooldownSessions:10,async isRelevant(){const e=f();return Boolean(("Apple_Terminal"===v.terminal?e.optionAsMetaKeyInstalled:e.shiftEnterKeyBindingInstalled)&&e.numStartups>3)}},{id:"shift-enter-setup",content:async()=>"Apple_Terminal"===v.terminal?"Ejecuta /terminal-setup para habilitar Option+Enter para nuevas líneas":"Ejecuta /terminal-setup para habilitar Shift+Enter para nuevas líneas",cooldownSessions:10,async isRelevant(){if(!i())return!1;const e=f();return!("Apple_Terminal"===v.terminal?e.optionAsMetaKeyInstalled:e.shiftEnterKeyBindingInstalled)}},{id:"memory-command",content:async()=>"Usa /memory para ver y gestionar la memoria de Context",cooldownSessions:15,isRelevant:async()=>f().memoryUsageCount<=0},{id:"theme-command",content:async()=>"Usa /theme para cambiar el tema de color",cooldownSessions:20,isRelevant:async()=>!0},{id:"colorterm-truecolor",content:async()=>"Prueba a configurar la variable de entorno COLORTERM=truecolor para obtener colores más vivos",cooldownSessions:30,isRelevant:async()=>!process.env.COLORTERM&&e.level<3},{id:"powershell-tool-env",content:async()=>"Configura CONTEXT_CODE_USE_POWERSHELL_TOOL=1 para habilitar la herramienta PowerShell (vista previa)",cooldownSessions:10,isRelevant:async()=>"windows"===x()&&void 0===process.env.CONTEXT_CODE_USE_POWERSHELL_TOOL&&void 0===process.env.CLAUDE_CODE_USE_POWERSHELL_TOOL},{id:"status-line",content:async()=>"Usa /statusline para configurar una línea de estado personalizada que se mostrará debajo del cuadro de entrada",cooldownSessions:25,isRelevant:async()=>void 0===t().statusLine},{id:"prompt-queue",content:async()=>"Pulsa Enter para poner en cola mensajes adicionales mientras Context está trabajando.",cooldownSessions:5,isRelevant:async()=>f().promptQueueUseCount<=3},{id:"enter-to-steer-in-relatime",content:async()=>"Envía mensajes a Context mientras trabaja para guiarlo en tiempo real",cooldownSessions:20,isRelevant:async()=>!0},{id:"todo-list",content:async()=>"Pide a Context que cree una lista de tareas cuando trabajes en tareas complejas para seguir el progreso",cooldownSessions:20,isRelevant:async()=>!0},{id:"vscode-command-install",content:async()=>`Abre la Paleta de Comandos (Cmd+Shift+P) y ejecuta "Shell Command: Install '${"vscode"===v.terminal?"code":v.terminal}' command in PATH" para habilitar la integración con el IDE`,cooldownSessions:0,async isRelevant(){if(!j())return!1;if("macos"!==x())return!1;switch(v.terminal){case"vscode":return!await E();case"cursor":return!await b();case"windsurf":return!await P();default:return!1}}},{id:"ide-upsell-external-terminal",content:async()=>"Conecta Context a tu IDE · /ide",cooldownSessions:4,async isRelevant(){if(C())return!1;if(0!==(await R()).length)return!1;return(await w()).length>0}},{id:"install-github-app",content:async()=>"Ejecuta /install-github-app para etiquetar a @claude directamente desde tus issues y PRs de Github",cooldownSessions:10,isRelevant:async()=>!f().githubActionSetupCount},{id:"install-slack-app",content:async()=>"Ejecuta /install-slack-app para usar Context en Slack",cooldownSessions:10,isRelevant:async()=>!f().slackAppInstallCount},{id:"permissions",content:async()=>"Usa /permissions para pre-aprobar y pre-denegar herramientas de bash, edición y MCP",cooldownSessions:10,isRelevant:async()=>f().numStartups>10},{id:"drag-and-drop-images",content:async()=>"¿Sabías que puedes arrastrar y soltar archivos de imagen en tu terminal?",cooldownSessions:10,isRelevant:async()=>!v.isSSH()},{id:"paste-images-mac",content:async()=>"Pega imágenes en Context Code usando control+v (¡no cmd+v!)",cooldownSessions:10,isRelevant:async()=>"macos"===x()},{id:"double-esc",content:async()=>"Pulsa dos veces esc para rebobinar la conversación a un punto anterior en el tiempo",cooldownSessions:10,isRelevant:async()=>!a()},{id:"double-esc-code-restore",content:async()=>"Pulsa dos veces esc para rebobinar el código y/o la conversación a un punto anterior en el tiempo",cooldownSessions:10,isRelevant:async()=>a()},{id:"continue",content:async()=>"Ejecuta context --continue o context --resume para reanudar una conversación",cooldownSessions:10,isRelevant:async()=>!0},{id:"rename-conversation",content:async()=>"Nombra tus conversaciones con /rename para encontrarlas fácilmente en /resume más tarde",cooldownSessions:15,isRelevant:async()=>M()&&f().numStartups>10},{id:"custom-commands",content:async()=>"Crea habilidades añadiendo archivos .md a .context/skills/ en tu proyecto o ~/.context/skills/ para habilidades globales",cooldownSessions:15,isRelevant:async()=>f().numStartups>10},{id:"shift-tab",content:async()=>"ant"===process.env.USER_TYPE?`Pulsa ${d("chat:cycleMode","Chat","shift+tab")} para alternar entre el modo predeterminado y el modo automático`:`Pulsa ${d("chat:cycleMode","Chat","shift+tab")} para alternar entre el modo predeterminado, el modo de edición con auto-aceptación y el modo de planificación`,cooldownSessions:10,isRelevant:async()=>!0},{id:"image-paste",content:async()=>`Usa ${d("chat:imagePaste","Chat","ctrl+v")} para pegar imágenes desde tu portapapeles`,cooldownSessions:20,isRelevant:async()=>!0},{id:"custom-agents",content:async()=>"Usa /agents para optimizar tareas específicas. Ej: Arquitecto de Software, Escritor de Código, Revisor de Código",cooldownSessions:15,isRelevant:async()=>f().numStartups>5},{id:"agent-flag",content:async()=>"Usa --agent <nombre_agente> para iniciar directamente una conversación con un subagente",cooldownSessions:15,isRelevant:async()=>f().numStartups>5},{id:"desktop-app",content:async()=>"Ejecuta Context Code local o remotamente usando la aplicación de escritorio: clau.de/desktop",cooldownSessions:15,isRelevant:async()=>"linux"!==x()},{id:"desktop-shortcut",content:async e=>`Continúa tu sesión en Context Code Desktop con ${l("suggestion",e.theme)("/desktop")}`,cooldownSessions:15,isRelevant:async()=>!!r().enable_shortcut_tip&&("darwin"===process.platform||"win32"===process.platform&&"x64"===process.arch)},{id:"web-app",content:async()=>"Ejecuta tareas en la nube mientras sigues programando localmente · clau.de/web",cooldownSessions:15,isRelevant:async()=>!0},{id:"mobile-app",content:async()=>"/mobile para usar Context Code desde la aplicación de Context en tu teléfono",cooldownSessions:15,isRelevant:async()=>!0},{id:"opusplan-mode-reminder",content:async()=>`Tu modelo predeterminado es Opus Plan Mode. Presiona ${d("chat:cycleMode","Chat","shift+tab")} dos veces para activar el Modo Plan y planificar con Context Opus.`,cooldownSessions:2,async isRelevant(){if("ant"===process.env.USER_TYPE)return!1;const e=f(),n="opusplan"===T(),a=e.lastPlanModeUse?(Date.now()-e.lastPlanModeUse)/864e5:1/0;return n&&a>3}},{id:"frontend-design-plugin",content:async e=>`¿Trabajando con HTML/CSS? Instala el plugin frontend-design:\n${l("suggestion",e.theme)(`/plugin install frontend-design@${O}`)}`,cooldownSessions:3,isRelevant:async e=>isMarketplacePluginRelevant("frontend-design",e,{filePath:/\.(html|css|htm)$/i})},{id:"vercel-plugin",content:async e=>`¿Trabajando con Vercel? Instala el plugin vercel:\n${l("suggestion",e.theme)(`/plugin install vercel@${O}`)}`,cooldownSessions:3,isRelevant:async e=>isMarketplacePluginRelevant("vercel",e,{filePath:/(?:^|[/\\])vercel\.json$/i,cli:["vercel"]})},{id:"effort-high-nudge",content:async e=>{const n=l("suggestion",e.theme)("/effort high");return"copy_b"===L("tengu_tide_elm","off")?`Usa ${n} para obtener mejores respuestas directas. Context lo analiza profundamente primero.`:`¿Trabajando en algo difícil? ${n} ofrece mejores respuestas iniciales`},cooldownSessions:3,isRelevant:async()=>{if(!p())return!1;if(!g(_()))return!1;if(void 0!==o("policySettings")?.effortLevel)return!1;if(void 0!==y())return!1;const e=s().effortLevel;return"high"!==e&&"max"!==e&&"off"!==L("tengu_tide_elm","off")}},{id:"subagent-fanout-nudge",content:async e=>{const n=l("suggestion",e.theme);return"copy_b"===L("tengu_tern_alloy","off")?`Para tareas grandes, pide a Context que ${n("use subagentes")}. Trabajan en paralelo y mantienen limpia la conversación principal.`:`Di ${n('"fan out subagents"')} y Context enviará un equipo. Cada uno profundiza para que no se escape nada.`},cooldownSessions:3,isRelevant:async()=>!!p()&&"off"!==L("tengu_tern_alloy","off")},{id:"loop-command-nudge",content:async e=>{const n=l("suggestion",e.theme);return"copy_b"===L("tengu_timber_lark","off")?`Usa ${n("/loop 5m check the deploy")} para ejecutar cualquier prompt de forma programada. Configúralo y olvídate.`:`${n("/loop")} ejecuta cualquier prompt de forma recurrente. Ideal para monitorear despliegues o estados de PR.`},cooldownSessions:3,isRelevant:async()=>!!p()&&(!!u()&&"off"!==L("tengu_timber_lark","off"))},{id:"guest-passes",content:async e=>{const n=l("context",e.theme),a=z();return a?`Comparte Context Code y gana ${n(q(a))} de uso extra · ${n("/passes")}`:`Tienes pases de invitado gratuitos para compartir · ${n("/passes")}`},cooldownSessions:3,isRelevant:async()=>{if(f().hasVisitedPasses)return!1;const{eligible:e}=I();return e}},{id:"overage-credit",content:async e=>{const n=l("context",e.theme),a=D(),s=a?A(a):null;return s?`${n(`${s} en uso extra, por nuestra cuenta`)} · aplicaciones de terceros · ${n("/extra-usage")}`:""},cooldownSessions:3,isRelevant:async()=>c()},{id:"feedback-command",content:async()=>"¡Usa /feedback para ayudarnos a mejorar!",cooldownSessions:15,async isRelevant(){if("ant"===process.env.USER_TYPE)return!1;return f().numStartups>5}}],B="ant"===process.env.USER_TYPE?[{id:"important-claudemd",content:async()=>'[ANT-ONLY] Use "IMPORTANT:" prefix for must-follow CLAUDE.md rules',cooldownSessions:30,isRelevant:async()=>!0},{id:"skillify",content:async()=>"[ANT-ONLY] Use /skillify at the end of a workflow to turn it into a reusable skill",cooldownSessions:15,isRelevant:async()=>!0}]:[];export async function getRelevantTips(e){const n=s().spinnerTipsOverride,a=function(){const e=s().spinnerTipsOverride;return e?.tips?.length?e.tips.map((e,n)=>({id:`custom-tip-${n}`,content:async()=>e,cooldownSessions:0,isRelevant:async()=>!0})):[]}();if(n?.excludeDefault&&a.length>0)return a;const t=[...Y,...B],o=await Promise.all(t.map(n=>n.isRelevant(e)));return[...t.filter((e,n)=>o[n]).filter(e=>H(e.id)>=e.cooldownSessions),...a]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
const e=30;export async function generateToolUseSummary({tools:n}){return 0===n.length?null:1===n.length?function(n){const o=t[n.name]??n.name,i=pickNoun(n.input);return clipTo(i?`${o} ${i}`:o,e)}(n[0]):function(n){const o=new Map,i=new Map;let s="";for(const e of n){const n=t[e.name]??e.name;if(o.set(n,(o.get(n)??0)+1),!s&&"object"==typeof e.input&&e.input){const t=e.input,n=t.file_path??t.path??t.filepath;"string"==typeof n&&(s=dirname(n))}if(!i.has(n)){const t=pickNoun(e.input);t&&i.set(n,basename(t))}}if(1===o.size){const[t,n]=[...o.entries()][0],r=i.get(t);if("Read"===t||"Wrote"===t||"Edited"===t){return clipTo(`${t} ${n} file${n>1?"s":""}${s?` in ${s}`:""}`,e)}return clipTo(r?`${t} ${r}`:`${t} ${n}`,e)}const r=[];for(const[e,t]of o){const n=i.get(e);if(n?r.push(`${e} ${n}`):r.push(`${e} ${t}`),3===r.length&&o.size>3){r.push(`+${o.size-3} more`);break}}return clipTo(r.join(", "),e)}(n)}const t={Read:"Read",Write:"Wrote",Edit:"Edited",Glob:"Listed paths in",Grep:"Searched in",Bash:"Ran",PowerShell:"Ran",WebFetch:"Fetched",WebSearch:"Searched",Agent:"Delegated to",TodoWrite:"Updated todos",ToolSearch:"Discovered tools",NotebookEdit:"Edited notebook",ListMcpResources:"Listed MCP resources",mcp__database__query:"Queried DB",mcp__database__list_tables:"Listed DB tables",mcp__database__describe_table:"Described table"};function pickNoun(e){if(!e||"object"!=typeof e)return"";const t=e;for(const e of["file_path","filepath","path","filePath"])if("string"==typeof t[e])return t[e];return"string"==typeof t.command?t.command:"string"==typeof t.pattern?t.pattern:"string"==typeof t.query?t.query:"string"==typeof t.url?t.url:"string"==typeof t.resource?t.resource:""}function clipTo(e,t){const n=e.trim();return n?n.length<=t?n:n.slice(0,t-1)+"…":null}function dirname(e){const t=Math.max(e.lastIndexOf("/"),e.lastIndexOf("\\"));return-1===t?"":e.slice(0,t)}function basename(e){const t=Math.max(e.lastIndexOf("/"),e.lastIndexOf("\\"));return-1===t?e:e.slice(t+1)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as e,jsxs as t}from"react/jsx-runtime";import{c as r}from"react/compiler-runtime";import n from"figures";import{Markdown as i}from"../../components/Markdown.js";import{BLACK_CIRCLE as o}from"../../constants/figures.js";import{Box as l,Text as m}from"../../ink.js";import{getDisplayPath as s}from"../../utils/file.js";import{formatFileSize as c}from"../../utils/format.js";import{formatBriefTimestamp as a}from"../../utils/formatBriefTimestamp.js";export function renderToolUseMessage(){return""}export function renderToolResultMessage(r,n,s){const c=(r.attachments?.length??0)>0;if(!r.message&&!c)return null;if(s?.isTranscriptMode)return t(l,{flexDirection:"row",marginTop:1,children:[e(l,{minWidth:2,children:e(m,{color:"text",children:o})}),t(l,{flexDirection:"column",children:[r.message?e(i,{children:r.message}):null,e(AttachmentList,{attachments:r.attachments})]})]});if(s?.isBriefOnly){const n=r.sentAt?a(r.sentAt):"";return t(l,{flexDirection:"column",marginTop:1,paddingLeft:2,children:[t(l,{flexDirection:"row",children:[e(m,{color:"
|
|
1
|
+
import{jsx as e,jsxs as t}from"react/jsx-runtime";import{c as r}from"react/compiler-runtime";import n from"figures";import{Markdown as i}from"../../components/Markdown.js";import{BLACK_CIRCLE as o}from"../../constants/figures.js";import{Box as l,Text as m}from"../../ink.js";import{getDisplayPath as s}from"../../utils/file.js";import{formatFileSize as c}from"../../utils/format.js";import{formatBriefTimestamp as a}from"../../utils/formatBriefTimestamp.js";export function renderToolUseMessage(){return""}export function renderToolResultMessage(r,n,s){const c=(r.attachments?.length??0)>0;if(!r.message&&!c)return null;if(s?.isTranscriptMode)return t(l,{flexDirection:"row",marginTop:1,children:[e(l,{minWidth:2,children:e(m,{color:"text",children:o})}),t(l,{flexDirection:"column",children:[r.message?e(i,{children:r.message}):null,e(AttachmentList,{attachments:r.attachments})]})]});if(s?.isBriefOnly){const n=r.sentAt?a(r.sentAt):"";return t(l,{flexDirection:"column",marginTop:1,paddingLeft:2,children:[t(l,{flexDirection:"row",children:[e(m,{color:"briefLabelContext",children:"Claude"}),n?t(m,{dimColor:!0,children:[" ",n]}):null]}),t(l,{flexDirection:"column",children:[r.message?e(i,{children:r.message}):null,e(AttachmentList,{attachments:r.attachments})]})]})}return t(l,{flexDirection:"row",marginTop:1,children:[e(l,{minWidth:2}),t(l,{flexDirection:"column",children:[r.message?e(i,{children:r.message}):null,e(AttachmentList,{attachments:r.attachments})]})]})}export function AttachmentList(t){const n=r(4),{attachments:i}=t;if(!i||0===i.length)return null;let o,m;return n[0]!==i?(o=i.map(_temp),n[0]=i,n[1]=o):o=n[1],n[2]!==o?(m=e(l,{flexDirection:"column",marginTop:1,children:o}),n[2]=o,n[3]=m):m=n[3],m}function _temp(r){return t(l,{flexDirection:"row",children:[t(m,{dimColor:!0,children:[n.pointerSmall," ",r.isImage?"[image]":"[file]"," "]}),e(m,{children:s(r.path)}),t(m,{dimColor:!0,children:[" (",c(r.size),")"]})]},r.path)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{spawn as r}from"child_process";import{join as o}from"path";import{
|
|
1
|
+
import{spawn as r}from"child_process";import{join as o,dirname as e}from"path";import{fileURLToPath as t}from"url";import{COMPUTER_CONTROL_MCP_SERVER_NAME as m}from"./common.js";import{ensureUvAvailable as p}from"../sembleMcp/setup.js";export function runComputerControlMcpServer(){const s=e(t(import.meta.url)),c=o(s,"server"),n=p(),i=r(n,["run","--project",c,"-m","computer_control_mcp.server"],{cwd:c,stdio:"inherit",env:{...process.env,PYTHONPATH:o(c,"src"),UV_SKIP_WHEEL_FILENAME_CHECK:"1"}});return i.on("error",r=>{console.error(`Failed to start ${m} server:`,r)}),i}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Auto detect text files and perform LF normalization
|
|
2
|
+
* text=auto
|
|
3
|
+
|
|
4
|
+
# Python files
|
|
5
|
+
*.py text diff=python
|
|
6
|
+
|
|
7
|
+
# Documentation
|
|
8
|
+
*.md text
|
|
9
|
+
*.rst text
|
|
10
|
+
*.txt text
|
|
11
|
+
|
|
12
|
+
# Exclude files from exporting
|
|
13
|
+
.gitattributes export-ignore
|
|
14
|
+
.gitignore export-ignore
|
|
15
|
+
.github export-ignore
|
|
16
|
+
.pytest_cache export-ignore
|
|
17
|
+
__pycache__ export-ignore
|
|
18
|
+
*.pyc export-ignore
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Use a lightweight Python base image
|
|
2
|
+
FROM python:3.12-slim
|
|
3
|
+
|
|
4
|
+
# Set environment variables for Python
|
|
5
|
+
ENV PYTHONDONTWRITEBYTECODE=1 \
|
|
6
|
+
PYTHONUNBUFFERED=1
|
|
7
|
+
|
|
8
|
+
# Set working directory
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
|
|
11
|
+
# Copy dependency file(s)
|
|
12
|
+
COPY pyproject.toml .
|
|
13
|
+
COPY src/ src/
|
|
14
|
+
COPY README.md README.md
|
|
15
|
+
|
|
16
|
+
# Install build backend (Hatchling)
|
|
17
|
+
RUN pip install --upgrade pip && \
|
|
18
|
+
pip install hatchling && \
|
|
19
|
+
pip install -e .
|
|
20
|
+
|
|
21
|
+
# Copy any additional files (e.g. configs, CLI, entrypoints)
|
|
22
|
+
COPY . .
|
|
23
|
+
|
|
24
|
+
# Default command (can be overridden)
|
|
25
|
+
CMD ["python", "-m", "computer_control_mcp"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 AB498 <abcd49800@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Computer Control MCP
|
|
2
|
+
|
|
3
|
+
### MCP server that provides computer control capabilities, like mouse, keyboard, OCR, etc. using PyAutoGUI, RapidOCR, ONNXRuntime. Similar to 'computer-use' by Anthropic. With Zero External Dependencies.
|
|
4
|
+
|
|
5
|
+
<div align="center" style="text-align:center;font-family: monospace; display: flex; align-items: center; justify-content: center; width: 100%; gap: 10px">
|
|
6
|
+
<a href="https://nextjs-boilerplate-ashy-nine-64.vercel.app/demo-computer-control"><img
|
|
7
|
+
src="https://komarev.com/ghpvc/?username=AB498&label=DEMO&style=for-the-badge&color=CC0000" /></a>
|
|
8
|
+
<a href="https://discord.gg/ZeeqSBpjU2"><img
|
|
9
|
+
src="https://img.shields.io/discord/1095854826786668545?style=for-the-badge&color=0000CC" alt="Discord"></a>
|
|
10
|
+
<a href="https://img.shields.io/badge/License-MIT-yellow.svg"><img
|
|
11
|
+
src="https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge&color=00CC00" alt="License: MIT"></a>
|
|
12
|
+
<a href="https://pypi.org/project/computer-control-mcp"><img
|
|
13
|
+
src="https://img.shields.io/pypi/v/computer-control-mcp?style=for-the-badge" alt="PyPi"></a>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
## Quick Usage (MCP Setup Using `uvx`)
|
|
21
|
+
|
|
22
|
+
***Note:** Running `uvx computer-control-mcp@latest` for the first time will download python dependencies (around 70MB) which may take some time. Recommended to run this in a terminal before using it as MCP. Subsequent runs will be instant.*
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"computer-control-mcp": {
|
|
28
|
+
"command": "uvx",
|
|
29
|
+
"args": ["computer-control-mcp@latest"]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
OR install globally with `pip`:
|
|
36
|
+
```bash
|
|
37
|
+
pip install computer-control-mcp
|
|
38
|
+
```
|
|
39
|
+
Then run the server with:
|
|
40
|
+
```bash
|
|
41
|
+
computer-control-mcp # instead of uvx computer-control-mcp, so you can use the latest version, also you can `uv cache clean` to clear the cache and `uvx` again to use latest version.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- Control mouse movements and clicks
|
|
47
|
+
- Type text at the current cursor position
|
|
48
|
+
- Take screenshots of the entire screen or specific windows with optional saving to downloads directory
|
|
49
|
+
- Extract text from screenshots using OCR (Optical Character Recognition)
|
|
50
|
+
- List and activate windows
|
|
51
|
+
- Press keyboard keys
|
|
52
|
+
- Drag and drop operations
|
|
53
|
+
- Enhanced screenshot capture for GPU-accelerated windows (Windows only)
|
|
54
|
+
|
|
55
|
+
## Note on GPU-accelerated Windows
|
|
56
|
+
|
|
57
|
+
Traditional screenshot methods like GDI/PrintWindow fail to capture GPU-accelerated windows, resulting in black screens. This impacts games, media players, Electron apps, browsers with GPU acceleration, streaming software, and CAD tools. Use WGC through `take_screenshot` tool's flag or `ENV` variable
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
### Custom Screenshot Directory
|
|
62
|
+
|
|
63
|
+
By default, screenshots are saved to the OS downloads directory. You can customize this by setting the `COMPUTER_CONTROL_MCP_SCREENSHOT_DIR` environment variable:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"computer-control-mcp": {
|
|
69
|
+
"command": "uvx",
|
|
70
|
+
"args": ["computer-control-mcp@latest"],
|
|
71
|
+
"env": {
|
|
72
|
+
"COMPUTER_CONTROL_MCP_SCREENSHOT_DIR": "C:\\Users\\YourName\\Pictures\\Screenshots"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or set it system-wide:
|
|
80
|
+
```bash
|
|
81
|
+
# Windows (PowerShell)
|
|
82
|
+
$env:COMPUTER_CONTROL_MCP_SCREENSHOT_DIR = "C:\Users\YourName\Pictures\Screenshots"
|
|
83
|
+
|
|
84
|
+
# macOS/Linux
|
|
85
|
+
export COMPUTER_CONTROL_MCP_SCREENSHOT_DIR="/home/yourname/Pictures/Screenshots"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If the specified directory doesn't exist, the server will fall back to the default downloads directory.
|
|
89
|
+
|
|
90
|
+
### Automatic WGC for Specific Windows
|
|
91
|
+
|
|
92
|
+
You can configure the system to automatically use Windows Graphics Capture (WGC) for specific windows by setting the `COMPUTER_CONTROL_MCP_WGC_PATTERNS` environment variable. This variable should contain comma-separated patterns that match window titles:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"mcpServers": {
|
|
97
|
+
"computer-control-mcp": {
|
|
98
|
+
"command": "uvx",
|
|
99
|
+
"args": ["computer-control-mcp@latest"],
|
|
100
|
+
"env": {
|
|
101
|
+
"COMPUTER_CONTROL_MCP_WGC_PATTERNS": "obs, discord, game, steam"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Or set it system-wide:
|
|
109
|
+
```bash
|
|
110
|
+
# Windows (PowerShell)
|
|
111
|
+
$env:COMPUTER_CONTROL_MCP_WGC_PATTERNS = "obs, discord, game, steam"
|
|
112
|
+
|
|
113
|
+
# macOS/Linux
|
|
114
|
+
export COMPUTER_CONTROL_MCP_WGC_PATTERNS="obs, discord, game, steam"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
When this variable is set, any window whose title contains any of the specified patterns will automatically use WGC for screenshot capture, eliminating black screens for GPU-accelerated applications.
|
|
118
|
+
|
|
119
|
+
## Available Tools
|
|
120
|
+
|
|
121
|
+
### Mouse Control
|
|
122
|
+
- `click_screen(x: int, y: int)`: Click at specified screen coordinates
|
|
123
|
+
- `move_mouse(x: int, y: int)`: Move mouse cursor to specified coordinates
|
|
124
|
+
- `drag_mouse(from_x: int, from_y: int, to_x: int, to_y: int, duration: float = 0.5)`: Drag mouse from one position to another
|
|
125
|
+
- `mouse_down(button: str = "left")`: Hold down a mouse button ('left', 'right', 'middle')
|
|
126
|
+
- `mouse_up(button: str = "left")`: Release a mouse button ('left', 'right', 'middle')
|
|
127
|
+
|
|
128
|
+
### Keyboard Control
|
|
129
|
+
- `type_text(text: str)`: Type the specified text at current cursor position
|
|
130
|
+
- `press_key(key: str)`: Press a specified keyboard key
|
|
131
|
+
- `key_down(key: str)`: Hold down a specific keyboard key until released
|
|
132
|
+
- `key_up(key: str)`: Release a specific keyboard key
|
|
133
|
+
- `press_keys(keys: Union[str, List[Union[str, List[str]]]])`: Press keyboard keys (supports single keys, sequences, and combinations)
|
|
134
|
+
|
|
135
|
+
### Screen and Window Management
|
|
136
|
+
- `take_screenshot(title_pattern: str = None, use_regex: bool = False, threshold: int = 60, scale_percent_for_ocr: int = None, save_to_downloads: bool = False, use_wgc: bool = False)`: Capture screen or window
|
|
137
|
+
- `take_screenshot_with_ocr(title_pattern: str = None, use_regex: bool = False, threshold: int = 10, scale_percent_for_ocr: int = None, save_to_downloads: bool = False)`: Extract adn return text with coordinates using OCR from screen or window
|
|
138
|
+
- `get_screen_size()`: Get current screen resolution
|
|
139
|
+
- `list_windows()`: List all open windows
|
|
140
|
+
- `activate_window(title_pattern: str, use_regex: bool = False, threshold: int = 60)`: Bring specified window to foreground
|
|
141
|
+
- `wait_milliseconds(milliseconds: int)`: Wait for a specified number of milliseconds
|
|
142
|
+
|
|
143
|
+
## Development
|
|
144
|
+
|
|
145
|
+
### Setting up the Development Environment
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Clone the repository
|
|
149
|
+
git clone https://github.com/AB498/computer-control-mcp.git
|
|
150
|
+
cd computer-control-mcp
|
|
151
|
+
|
|
152
|
+
# Build/Run:
|
|
153
|
+
|
|
154
|
+
# 1. Install in development mode | Meaning that your edits to source code will be reflected in the installed package.
|
|
155
|
+
pip install -e .
|
|
156
|
+
|
|
157
|
+
# Then Start server | This is equivalent to `uvx computer-control-mcp@latest` just the local code is used
|
|
158
|
+
computer-control-mcp
|
|
159
|
+
|
|
160
|
+
# -- OR --
|
|
161
|
+
|
|
162
|
+
# 2. Build after `pip install hatch` | This needs version increment in orer to reflect code changes
|
|
163
|
+
hatch build
|
|
164
|
+
|
|
165
|
+
# Windows
|
|
166
|
+
$latest = Get-ChildItem .\dist\*.whl | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
|
167
|
+
pip install $latest.FullName --upgrade
|
|
168
|
+
|
|
169
|
+
# Non-windows
|
|
170
|
+
pip install dist/*.whl --upgrade
|
|
171
|
+
|
|
172
|
+
# Run
|
|
173
|
+
computer-control-mcp
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Running Tests
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
python -m pytest
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## API Reference
|
|
183
|
+
|
|
184
|
+
See the [API Reference](docs/api.md) for detailed information about the available functions and classes.
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT
|
|
189
|
+
|
|
190
|
+
## For more information or help
|
|
191
|
+
|
|
192
|
+
- [Email (abcd49800@gmail.com)](mailto:abcd49800@gmail.com)
|
|
193
|
+
- [Discord (CodePlayground)](https://discord.gg/ZeeqSBpjU2)
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "computer-control-mcp"
|
|
7
|
+
version = "0.3.10"
|
|
8
|
+
description = "MCP server that provides computer control capabilities, like mouse, keyboard, OCR, etc. using PyAutoGUI, RapidOCR, ONNXRuntime. Similar to 'computer-use' by Anthropic. With Zero External Dependencies."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{name = "AB498", email = "abcd49800@gmail.com"}]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Topic :: Software Development :: Libraries",
|
|
20
|
+
"Topic :: Utilities"
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"pyautogui==0.9.54",
|
|
24
|
+
"mcp[cli]==1.13.0",
|
|
25
|
+
"pillow==11.3.0",
|
|
26
|
+
"pygetwindow==0.0.9; sys_platform=='win32'",
|
|
27
|
+
"pywinctl==0.4.1",
|
|
28
|
+
"fuzzywuzzy==0.18.0",
|
|
29
|
+
"rapidocr==3.3.1",
|
|
30
|
+
"onnxruntime==1.22.0",
|
|
31
|
+
"rapidocr_onnxruntime==1.2.3",
|
|
32
|
+
"opencv-python==4.12.0.88",
|
|
33
|
+
"python-Levenshtein>=0.20.9",
|
|
34
|
+
"mss>=7.0.0",
|
|
35
|
+
"windows-capture>=1.0.0; sys_platform=='win32'"
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/AB498/computer-control-mcp"
|
|
40
|
+
Issues = "https://github.com/AB498/computer-control-mcp/issues"
|
|
41
|
+
Documentation = "https://github.com/AB498/computer-control-mcp#readme"
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
computer-control-mcp = "computer_control_mcp.cli:main"
|
|
45
|
+
computer-control-mcp-server = "computer_control_mcp.server:main"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build]
|
|
48
|
+
sources = ["src"]
|
|
49
|
+
packages = ["src/computer_control_mcp"]
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["src/computer_control_mcp"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
|
|
2
|
+
|
|
3
|
+
startCommand:
|
|
4
|
+
type: stdio
|
|
5
|
+
configSchema:
|
|
6
|
+
# JSON Schema defining the configuration options for the MCP.
|
|
7
|
+
type: object
|
|
8
|
+
description: Empty config
|
|
9
|
+
commandFunction:
|
|
10
|
+
# A JS function that produces the CLI command based on the given config to start the MCP on stdio.
|
|
11
|
+
|-
|
|
12
|
+
(config) => ({ command: 'python', args: ['src/computer_control_mcp/core.py'] })
|
|
13
|
+
exampleConfig: {}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Computer Control MCP Source Code
|
|
2
|
+
|
|
3
|
+
This directory contains the source code for the Computer Control MCP package.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
- `computer_control_mcp/`: Main package directory
|
|
8
|
+
- `__init__.py`: Package initialization
|
|
9
|
+
- `__main__.py`: Entry point for running as a module
|
|
10
|
+
- `core.py`: Core functionality
|
|
11
|
+
- `cli.py`: Command-line interface
|
|
12
|
+
- `gui.py`: Graphical user interface for testing
|
|
Binary file
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Computer Control MCP - Python package for computer control via MCP.
|
|
3
|
+
|
|
4
|
+
This package provides computer control capabilities using PyAutoGUI through a
|
|
5
|
+
Model Context Protocol (MCP) server.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from computer_control_mcp.core import mcp, main
|
|
9
|
+
|
|
10
|
+
__version__ = "0.1.2"
|
|
11
|
+
__all__ = ["mcp", "main"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Entry point for running the Computer Control MCP as a module.
|
|
3
|
+
|
|
4
|
+
This module serves as the main entry point for the package.
|
|
5
|
+
When executed directly (e.g., with `python -m computer_control_mcp`),
|
|
6
|
+
it will run the CLI interface.
|
|
7
|
+
|
|
8
|
+
For CLI functionality, use:
|
|
9
|
+
computer-control-mcp <command>
|
|
10
|
+
python -m computer_control_mcp <command>
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from computer_control_mcp.cli import main as cli_main
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
"""Main entry point for the package."""
|
|
17
|
+
# Run the CLI when the module is executed directly
|
|
18
|
+
cli_main()
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
main()
|