@iaforged/context-code 2.3.7 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/context-bootstrap.js +2 -2
  2. package/contextcode-bootstrap.js +1 -1
  3. package/dist/src/cli/handlers/auth.js +1 -1
  4. package/dist/src/commands/agent-spawn/agentSpawn.js +1 -0
  5. package/dist/src/commands/agent-spawn/index.js +1 -0
  6. package/dist/src/commands/login/login.js +1 -1
  7. package/dist/src/commands/skills/skills.js +1 -1
  8. package/dist/src/commands/swarm-init/index.js +1 -1
  9. package/dist/src/commands/swarm-init/swarmInit.js +1 -1
  10. package/dist/src/commands/swarm-list-teams/index.js +1 -0
  11. package/dist/src/commands/swarm-list-teams/swarmListTeams.js +1 -0
  12. package/dist/src/commands.js +1 -1
  13. package/dist/src/components/ConsoleOAuthFlow.js +1 -1
  14. package/dist/src/components/agents/agentFileUtils.js +1 -1
  15. package/dist/src/components/agents/new-agent-creation/wizard-steps/LocationStep.js +1 -1
  16. package/dist/src/components/agents/new-agent-creation/wizard-steps/MemoryStep.js +1 -1
  17. package/dist/src/constants/oauth.js +1 -1
  18. package/dist/src/core/agents/blueprints.js +1 -1
  19. package/dist/src/core/agents/teams.js +1 -0
  20. package/dist/src/projectOnboardingState.js +1 -1
  21. package/dist/src/screens/Doctor.js +1 -1
  22. package/dist/src/services/api/openai.js +1 -1
  23. package/dist/src/services/oauth/copilotDevice.js +1 -0
  24. package/dist/src/skills/loadSkillsDir.js +1 -1
  25. package/dist/src/tools/AgentTool/agentMemory.js +1 -1
  26. package/dist/src/utils/auth.js +1 -1
  27. package/dist/src/utils/claudemd.js +1 -1
  28. package/dist/src/utils/cronTasks.js +1 -1
  29. package/dist/src/utils/cronTasksLock.js +1 -1
  30. package/dist/src/utils/doctorDiagnostic.js +1 -1
  31. package/dist/src/utils/envUtils.js +1 -1
  32. package/dist/src/utils/hooks/skillImprovement.js +1 -1
  33. package/dist/src/utils/markdownConfigLoader.js +1 -1
  34. package/dist/src/utils/metaMcp/common.js +1 -0
  35. package/dist/src/utils/metaMcp/mcpServer.js +1 -0
  36. package/dist/src/utils/metaMcp/server/index.js +1 -0
  37. package/dist/src/utils/metaMcp/setup.js +1 -0
  38. package/dist/src/utils/model/model.js +1 -1
  39. package/dist/src/utils/model/modelOptions.js +1 -1
  40. package/dist/src/utils/model/providerCatalog.js +1 -1
  41. package/dist/src/utils/model/providerModels.js +1 -1
  42. package/dist/src/utils/model/providerProfiles.js +1 -1
  43. package/dist/src/utils/model/providerProfilesDb.js +1 -1
  44. package/dist/src/utils/model/providers.js +1 -1
  45. package/dist/src/utils/nativeInstaller/installer.js +1 -1
  46. package/dist/src/utils/permissions/filesystem.js +1 -1
  47. package/dist/src/utils/plugins/addDirPluginSettings.js +1 -1
  48. package/dist/src/utils/sandbox/sandbox-adapter.js +1 -1
  49. package/dist/src/utils/settings/settings.js +1 -1
  50. package/dist/src/utils/skills/skillChangeDetector.js +1 -1
  51. package/dist/src/utils/worktree.js +1 -1
  52. package/dist/src/whatsapp/config.js +1 -1
  53. package/dist/webapp/chunk-VAB2VXFI.js +1 -1
  54. package/dist/webapp/main-MTQLKGXD.js +1 -1
  55. package/dist/webapp/polyfills-7R4CRVNH.js +1 -1
  56. package/package.json +5 -3
@@ -1 +1 @@
1
- import{isEnvTruthy as e}from"../envUtils.js";import{getSecureStorage as r}from"../secureStorage/index.js";import{isProviderBaseUrlCustomized as i}from"./providerBaseUrls.js";import{getProviderOverride as o}from"./providerOverrideContext.js";import{getStoredActiveProviderPreference as n}from"./providerProfilesDb.js";import{listProviderProfiles as t}from"./providerProfiles.js";export function providerPreferenceToApiProvider(e){if(e)return"claude"===e?"firstParty":e}export function isOpenAIProviderConfigured(){return e(process.env.CLAUDE_CODE_USE_OPENAI)||Boolean(process.env.OPENAI_API_KEY)||Boolean(process.env.OPENAI_API_TOKEN)||Boolean(process.env.OPENAI_OAUTH_TOKEN)||function(){try{const e=r().read(),i=Object.entries(e?.providerProfileOauth??{}).some(([e,r])=>e.startsWith("openai/")&&"object"==typeof r&&null!==r&&"string"==typeof r.accessToken&&r.accessToken.trim());return Boolean(i||e?.openAiOauth?.accessToken)}catch{return!1}}()}export function isOpenRouterProviderConfigured(){try{const e=r().read(),i=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("openrouter/")&&"string"==typeof r&&r.trim());return Boolean(process.env.OPENROUTER_API_KEY||process.env.OPENROUTER_API_TOKEN||e?.providerApiKeys?.openrouter||i)}catch{return Boolean(process.env.OPENROUTER_API_KEY||process.env.OPENROUTER_API_TOKEN)}}export function isOllamaProviderConfigured(){try{const e=n();return Boolean(process.env.OLLAMA_BASE_URL||i("ollama")||t("ollama").length>0||"ollama"===e)}catch{return Boolean(process.env.OLLAMA_BASE_URL)}}export function isOllamaCloudProviderConfigured(){if(isOllamaProviderConfigured())return!0;try{const e=r().read(),o=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("ollama-cloud/")&&"string"==typeof r&&r.trim()),t=n();return Boolean(process.env.OLLAMA_CLOUD_BASE_URL||process.env.OLLAMA_BASE_URL||process.env.OLLAMA_API_KEY||e?.providerApiKeys?.["ollama-cloud"]||o||i("ollama-cloud")||"ollama-cloud"===t||"ollama"===t)}catch{return Boolean(process.env.OLLAMA_CLOUD_BASE_URL||process.env.OLLAMA_BASE_URL||process.env.OLLAMA_API_KEY)}}export function isGeminiApiProviderConfigured(){try{const e=r().read(),i=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("gemini-api/")&&"string"==typeof r&&r.trim());return Boolean(process.env.GEMINI_API_KEY||process.env.GOOGLE_API_KEY||e?.providerApiKeys?.["gemini-api"]||i)}catch{return Boolean(process.env.GEMINI_API_KEY||process.env.GOOGLE_API_KEY)}}export function isGeminiGoogleProviderConfigured(){try{const e=r().read(),i=n();return Boolean(process.env.GEMINI_OAUTH_TOKEN||process.env.GOOGLE_OAUTH_ACCESS_TOKEN||process.env.GOOGLE_APPLICATION_CREDENTIALS||process.env.GEMINI_GOOGLE_PROJECT_ID||process.env.GOOGLE_CLOUD_PROJECT||process.env.GCLOUD_PROJECT||Object.entries(e?.providerProfileOauth??{}).some(([e,r])=>e.startsWith("gemini-google/")&&"object"==typeof r&&null!==r&&"string"==typeof r.accessToken&&r.accessToken.trim())||"gemini-google"===i)}catch{return Boolean(process.env.GEMINI_OAUTH_TOKEN||process.env.GOOGLE_OAUTH_ACCESS_TOKEN||process.env.GOOGLE_APPLICATION_CREDENTIALS||process.env.GEMINI_GOOGLE_PROJECT_ID||process.env.GOOGLE_CLOUD_PROJECT||process.env.GCLOUD_PROJECT)}}export function isZAIProviderConfigured(){try{const e=r().read(),i=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("zai/")&&"string"==typeof r&&r.trim());return Boolean(process.env.ZAI_API_KEY||e?.providerApiKeys?.zai||i)}catch{return Boolean(process.env.ZAI_API_KEY)}}export function isMiniMaxProviderConfigured(){try{const e=r().read(),i=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("minimax/")&&"string"==typeof r&&r.trim());return Boolean(process.env.MINIMAX_API_KEY||e?.providerApiKeys?.minimax||i)}catch{return Boolean(process.env.MINIMAX_API_KEY)}}export function isNvidiaProviderConfigured(){try{const e=r().read(),i=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("nvidia/")&&"string"==typeof r&&r.trim());return Boolean(process.env.NVIDIA_API_KEY||e?.providerApiKeys?.nvidia||i)}catch{return Boolean(process.env.NVIDIA_API_KEY)}}export function isDeepSeekProviderConfigured(){try{const e=r().read(),i=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("deepseek/")&&"string"==typeof r&&r.trim());return Boolean(process.env.DEEPSEEK_API_KEY||e?.providerApiKeys?.deepseek||i)}catch{return Boolean(process.env.DEEPSEEK_API_KEY)}}export function isXaiProviderConfigured(){return isApiKeyProviderConfigured("xai","XAI_API_KEY")}export const OPENAI_COMPAT_API_KEY_PROVIDERS=[{id:"gmi",envVar:"GMI_API_KEY"},{id:"novita",envVar:"NOVITA_API_KEY"},{id:"stepfun",envVar:"STEPFUN_API_KEY"},{id:"huggingface",envVar:"HF_TOKEN"},{id:"opencode-zen",envVar:"OPENCODE_ZEN_API_KEY"},{id:"arcee",envVar:"ARCEEAI_API_KEY"},{id:"alibaba",envVar:"DASHSCOPE_API_KEY"},{id:"kimi",envVar:"KIMI_API_KEY"}];const s=new Set(OPENAI_COMPAT_API_KEY_PROVIDERS.map(e=>e.id));export function isApiKeyProviderConfigured(e,i){try{const o=r().read(),n=Object.entries(o?.providerProfileApiKeys??{}).some(([r,i])=>r.startsWith(`${e}/`)&&"string"==typeof i&&i.trim());return Boolean(i&&process.env[i]||o?.providerApiKeys?.[e]||n)}catch{return Boolean(i&&process.env[i])}}export function isOpenAICompatibleProvider(e){return"openai"===e||"openrouter"===e||"ollama"===e||"ollama-cloud"===e||"gemini-api"===e||"gemini-google"===e||"zai"===e||"minimax"===e||"nvidia"===e||"deepseek"===e||"xai"===e||s.has(e)||"custom-openai"===e}export function usesOpenAIBackend(e){return isOpenAICompatibleProvider(e)&&"minimax"!==e}export function hasDualProviderSessions(){return(isOpenAIProviderConfigured()||isOpenRouterProviderConfigured()||isOllamaProviderConfigured()||isOllamaCloudProviderConfigured()||isGeminiApiProviderConfigured()||isGeminiGoogleProviderConfigured()||isZAIProviderConfigured()||isMiniMaxProviderConfigured()||isNvidiaProviderConfigured()||isDeepSeekProviderConfigured()||isXaiProviderConfigured()||OPENAI_COMPAT_API_KEY_PROVIDERS.some(e=>isApiKeyProviderConfigured(e.id,e.envVar)))&&function(){try{const e=r().read(),i=Object.entries(e?.providerProfileOauth??{}).some(([e,r])=>e.startsWith("claude/")&&"object"==typeof r&&null!==r&&"string"==typeof r.accessToken&&r.accessToken.trim());return Boolean(i)}catch{return!1}}()}export function getAPIProvider(){const r=o();if(r)return r;const i=process.env.CONTEXT_ACTIVE_PROVIDER||process.env.CLAUDE_PROVIDER;if(i)return i;if(e(process.env.CLAUDE_CODE_USE_BEDROCK))return"bedrock";if(e(process.env.CLAUDE_CODE_USE_VERTEX))return"vertex";if(e(process.env.CLAUDE_CODE_USE_FOUNDRY))return"foundry";try{const e=n();if("claude"===e)return"firstParty";if("openai"===e)return"openai";if("openrouter"===e)return"openrouter";if("ollama"===e)return"ollama";if("ollama-cloud"===e)return"ollama-cloud";if("gemini-api"===e)return"gemini-api";if("gemini-google"===e)return"gemini-google";if("zai"===e)return"zai";if("minimax"===e)return"minimax";if("nvidia"===e)return"nvidia";if("deepseek"===e)return"deepseek";if("xai"===e)return"xai";if(e&&s.has(e))return e;if("custom-openai"===e)return"custom-openai";if("custom-anthropic"===e)return"custom-anthropic"}catch{}const t=isOpenAIProviderConfigured(),a=isOpenRouterProviderConfigured(),p=isOllamaProviderConfigured(),c=isOllamaCloudProviderConfigured(),u=isGeminiApiProviderConfigured(),d=isGeminiGoogleProviderConfigured(),v=isZAIProviderConfigured(),P=isMiniMaxProviderConfigured(),A=isNvidiaProviderConfigured(),_=isDeepSeekProviderConfigured(),f=isXaiProviderConfigured();if(t)return"openai";if(a)return"openrouter";if(v)return"zai";if(P)return"minimax";if(A)return"nvidia";if(_)return"deepseek";if(f)return"xai";for(const{id:e,envVar:r}of OPENAI_COMPAT_API_KEY_PROVIDERS)if(isApiKeyProviderConfigured(e,r))return e;return u?"gemini-api":d?"gemini-google":c?"ollama-cloud":p?"ollama":"firstParty"}export function getAPIProviderForStatsig(){return getAPIProvider()}export function isFirstPartyAnthropicBaseUrl(){const e=process.env.ANTHROPIC_BASE_URL;if(!e)return!0;try{const r=new URL(e).host,i=["api.anthropic.com"];return"ant"===process.env.USER_TYPE&&i.push("api-staging.anthropic.com"),i.includes(r)}catch{return!1}}
1
+ import{isEnvTruthy as e}from"../envUtils.js";import{getSecureStorage as r}from"../secureStorage/index.js";import{isProviderBaseUrlCustomized as o}from"./providerBaseUrls.js";import{getProviderOverride as i}from"./providerOverrideContext.js";import{getStoredActiveProviderPreference as n}from"./providerProfilesDb.js";import{listProviderProfiles as t}from"./providerProfiles.js";export function providerPreferenceToApiProvider(e){if(e)return"claude"===e?"firstParty":e}export function isOpenAIProviderConfigured(){return e(process.env.CLAUDE_CODE_USE_OPENAI)||Boolean(process.env.OPENAI_API_KEY)||Boolean(process.env.OPENAI_API_TOKEN)||Boolean(process.env.OPENAI_OAUTH_TOKEN)||function(){try{const e=r().read(),o=Object.entries(e?.providerProfileOauth??{}).some(([e,r])=>e.startsWith("openai/")&&"object"==typeof r&&null!==r&&"string"==typeof r.accessToken&&r.accessToken.trim());return Boolean(o||e?.openAiOauth?.accessToken)}catch{return!1}}()}export function isOpenRouterProviderConfigured(){try{const e=r().read(),o=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("openrouter/")&&"string"==typeof r&&r.trim());return Boolean(process.env.OPENROUTER_API_KEY||process.env.OPENROUTER_API_TOKEN||e?.providerApiKeys?.openrouter||o)}catch{return Boolean(process.env.OPENROUTER_API_KEY||process.env.OPENROUTER_API_TOKEN)}}export function isOllamaProviderConfigured(){try{const e=n();return Boolean(process.env.OLLAMA_BASE_URL||o("ollama")||t("ollama").length>0||"ollama"===e)}catch{return Boolean(process.env.OLLAMA_BASE_URL)}}export function isOllamaCloudProviderConfigured(){if(isOllamaProviderConfigured())return!0;try{const e=r().read(),i=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("ollama-cloud/")&&"string"==typeof r&&r.trim()),t=n();return Boolean(process.env.OLLAMA_CLOUD_BASE_URL||process.env.OLLAMA_BASE_URL||process.env.OLLAMA_API_KEY||e?.providerApiKeys?.["ollama-cloud"]||i||o("ollama-cloud")||"ollama-cloud"===t||"ollama"===t)}catch{return Boolean(process.env.OLLAMA_CLOUD_BASE_URL||process.env.OLLAMA_BASE_URL||process.env.OLLAMA_API_KEY)}}export function isGeminiApiProviderConfigured(){try{const e=r().read(),o=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("gemini-api/")&&"string"==typeof r&&r.trim());return Boolean(process.env.GEMINI_API_KEY||process.env.GOOGLE_API_KEY||e?.providerApiKeys?.["gemini-api"]||o)}catch{return Boolean(process.env.GEMINI_API_KEY||process.env.GOOGLE_API_KEY)}}export function isGeminiGoogleProviderConfigured(){try{const e=r().read(),o=n();return Boolean(process.env.GEMINI_OAUTH_TOKEN||process.env.GOOGLE_OAUTH_ACCESS_TOKEN||process.env.GOOGLE_APPLICATION_CREDENTIALS||process.env.GEMINI_GOOGLE_PROJECT_ID||process.env.GOOGLE_CLOUD_PROJECT||process.env.GCLOUD_PROJECT||Object.entries(e?.providerProfileOauth??{}).some(([e,r])=>e.startsWith("gemini-google/")&&"object"==typeof r&&null!==r&&"string"==typeof r.accessToken&&r.accessToken.trim())||"gemini-google"===o)}catch{return Boolean(process.env.GEMINI_OAUTH_TOKEN||process.env.GOOGLE_OAUTH_ACCESS_TOKEN||process.env.GOOGLE_APPLICATION_CREDENTIALS||process.env.GEMINI_GOOGLE_PROJECT_ID||process.env.GOOGLE_CLOUD_PROJECT||process.env.GCLOUD_PROJECT)}}export function isCopilotProviderConfigured(){try{const e=r().read(),o=n();return Boolean(process.env.GITHUB_COPILOT_TOKEN||process.env.COPILOT_OAUTH_TOKEN||Object.entries(e?.providerProfileOauth??{}).some(([e,r])=>e.startsWith("copilot/")&&"object"==typeof r&&null!==r&&("string"==typeof r.githubToken&&r.githubToken.trim()||"string"==typeof r.accessToken&&r.accessToken.trim()))||"copilot"===o)}catch{return Boolean(process.env.GITHUB_COPILOT_TOKEN||process.env.COPILOT_OAUTH_TOKEN)}}export function isZAIProviderConfigured(){try{const e=r().read(),o=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("zai/")&&"string"==typeof r&&r.trim());return Boolean(process.env.ZAI_API_KEY||e?.providerApiKeys?.zai||o)}catch{return Boolean(process.env.ZAI_API_KEY)}}export function isMiniMaxProviderConfigured(){try{const e=r().read(),o=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("minimax/")&&"string"==typeof r&&r.trim());return Boolean(process.env.MINIMAX_API_KEY||e?.providerApiKeys?.minimax||o)}catch{return Boolean(process.env.MINIMAX_API_KEY)}}export function isNvidiaProviderConfigured(){try{const e=r().read(),o=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("nvidia/")&&"string"==typeof r&&r.trim());return Boolean(process.env.NVIDIA_API_KEY||e?.providerApiKeys?.nvidia||o)}catch{return Boolean(process.env.NVIDIA_API_KEY)}}export function isDeepSeekProviderConfigured(){try{const e=r().read(),o=Object.entries(e?.providerProfileApiKeys??{}).some(([e,r])=>e.startsWith("deepseek/")&&"string"==typeof r&&r.trim());return Boolean(process.env.DEEPSEEK_API_KEY||e?.providerApiKeys?.deepseek||o)}catch{return Boolean(process.env.DEEPSEEK_API_KEY)}}export function isXaiProviderConfigured(){return isApiKeyProviderConfigured("xai","XAI_API_KEY")}export const OPENAI_COMPAT_API_KEY_PROVIDERS=[{id:"gmi",envVar:"GMI_API_KEY"},{id:"novita",envVar:"NOVITA_API_KEY"},{id:"stepfun",envVar:"STEPFUN_API_KEY"},{id:"huggingface",envVar:"HF_TOKEN"},{id:"opencode-zen",envVar:"OPENCODE_ZEN_API_KEY"},{id:"arcee",envVar:"ARCEEAI_API_KEY"},{id:"alibaba",envVar:"DASHSCOPE_API_KEY"},{id:"kimi",envVar:"KIMI_API_KEY"}];const s=new Set(OPENAI_COMPAT_API_KEY_PROVIDERS.map(e=>e.id));export function isApiKeyProviderConfigured(e,o){try{const i=r().read(),n=Object.entries(i?.providerProfileApiKeys??{}).some(([r,o])=>r.startsWith(`${e}/`)&&"string"==typeof o&&o.trim());return Boolean(o&&process.env[o]||i?.providerApiKeys?.[e]||n)}catch{return Boolean(o&&process.env[o])}}export function isOpenAICompatibleProvider(e){return"openai"===e||"openrouter"===e||"ollama"===e||"ollama-cloud"===e||"gemini-api"===e||"gemini-google"===e||"zai"===e||"minimax"===e||"nvidia"===e||"deepseek"===e||"xai"===e||"copilot"===e||s.has(e)||"custom-openai"===e}export function usesOpenAIBackend(e){return isOpenAICompatibleProvider(e)&&"minimax"!==e}export function hasDualProviderSessions(){return(isOpenAIProviderConfigured()||isOpenRouterProviderConfigured()||isOllamaProviderConfigured()||isOllamaCloudProviderConfigured()||isGeminiApiProviderConfigured()||isGeminiGoogleProviderConfigured()||isZAIProviderConfigured()||isMiniMaxProviderConfigured()||isNvidiaProviderConfigured()||isDeepSeekProviderConfigured()||isXaiProviderConfigured()||isCopilotProviderConfigured()||OPENAI_COMPAT_API_KEY_PROVIDERS.some(e=>isApiKeyProviderConfigured(e.id,e.envVar)))&&function(){try{const e=r().read(),o=Object.entries(e?.providerProfileOauth??{}).some(([e,r])=>e.startsWith("claude/")&&"object"==typeof r&&null!==r&&"string"==typeof r.accessToken&&r.accessToken.trim());return Boolean(o)}catch{return!1}}()}export function getAPIProvider(){const r=i();if(r)return r;const o=process.env.CONTEXT_ACTIVE_PROVIDER||process.env.CLAUDE_PROVIDER;if(o)return o;if(e(process.env.CLAUDE_CODE_USE_BEDROCK))return"bedrock";if(e(process.env.CLAUDE_CODE_USE_VERTEX))return"vertex";if(e(process.env.CLAUDE_CODE_USE_FOUNDRY))return"foundry";try{const e=n();if("claude"===e)return"firstParty";if("openai"===e)return"openai";if("openrouter"===e)return"openrouter";if("ollama"===e)return"ollama";if("ollama-cloud"===e)return"ollama-cloud";if("gemini-api"===e)return"gemini-api";if("gemini-google"===e)return"gemini-google";if("zai"===e)return"zai";if("minimax"===e)return"minimax";if("nvidia"===e)return"nvidia";if("deepseek"===e)return"deepseek";if("xai"===e)return"xai";if("copilot"===e)return"copilot";if(e&&s.has(e))return e;if("custom-openai"===e)return"custom-openai";if("custom-anthropic"===e)return"custom-anthropic"}catch{}const t=isOpenAIProviderConfigured(),a=isOpenRouterProviderConfigured(),p=isOllamaProviderConfigured(),c=isOllamaCloudProviderConfigured(),u=isGeminiApiProviderConfigured(),d=isGeminiGoogleProviderConfigured(),v=isZAIProviderConfigured(),P=isMiniMaxProviderConfigured(),f=isNvidiaProviderConfigured(),O=isDeepSeekProviderConfigured(),_=isXaiProviderConfigured(),A=isCopilotProviderConfigured();if(t)return"openai";if(A)return"copilot";if(a)return"openrouter";if(v)return"zai";if(P)return"minimax";if(f)return"nvidia";if(O)return"deepseek";if(_)return"xai";for(const{id:e,envVar:r}of OPENAI_COMPAT_API_KEY_PROVIDERS)if(isApiKeyProviderConfigured(e,r))return e;return u?"gemini-api":d?"gemini-google":c?"ollama-cloud":p?"ollama":"firstParty"}export function getAPIProviderForStatsig(){return getAPIProvider()}export function isFirstPartyAnthropicBaseUrl(){const e=process.env.ANTHROPIC_BASE_URL;if(!e)return!0;try{const r=new URL(e).host,o=["api.anthropic.com"];return"ant"===process.env.USER_TYPE&&o.push("api-staging.anthropic.com"),o.includes(r)}catch{return!1}}
@@ -1 +1 @@
1
- import{MACRO as e}from"../../recovery/bunBundleShim.js";import{constants as t}from"fs";import{access as a,chmod as i,copyFile as r,lstat as o,mkdir as n,readdir as s,readlink as c,realpath as l,rename as u,rm as d,rmdir as m,stat as p,symlink as f,unlink as _,writeFile as w}from"fs/promises";import{homedir as h}from"os";import{basename as y,delimiter as g,dirname as v,join as $,resolve as k}from"path";import{logEvent as b}from"../../services/analytics/index.js";import{getMaxVersion as P,shouldSkipVersion as x}from"../autoUpdater.js";import{registerCleanup as E}from"../cleanupRegistry.js";import{getGlobalConfig as A,saveGlobalConfig as F}from"../config.js";import{logForDebugging as L}from"../debug.js";import{getCurrentInstallationType as N}from"../doctorDiagnostic.js";import{env as C}from"../env.js";import{envDynamic as V}from"../envDynamic.js";import{isEnvTruthy as T}from"../envUtils.js";import{errorMessage as R,getErrnoCode as D,isENOENT as S,toError as B}from"../errors.js";import{execFileNoThrowWithCwd as I}from"../execFileNoThrow.js";import{getShellType as j}from"../localInstaller.js";import*as O from"../lockfile.js";import{logError as q}from"../log.js";import{gt as M,gte as U}from"../semver.js";import{filterClaudeAliases as W,getShellConfigPaths as H,readFileLines as K,writeFileLines as z}from"../shellConfig.js";import{sleep as Y}from"../sleep.js";import{getUserBinDir as G,getXDGCacheHome as X,getXDGDataHome as J,getXDGStateHome as Q}from"../xdg.js";import{downloadVersion as Z,getLatestVersion as ee}from"./download.js";import{acquireProcessLifetimeLock as te,cleanupStaleLocks as ae,isLockActive as ie,isPidBasedLockingEnabled as re,readLockContent as oe,withLock as ne}from"./pidLock.js";export const VERSION_RETENTION_COUNT=2;const se=6048e5;export function getPlatform(){const e=C.platform,t="x64"===process.arch?"x64":"arm64"===process.arch?"arm64":null;if(!t){const e=new Error(`Unsupported architecture: ${process.arch}`);throw L(`Native installer does not support architecture: ${process.arch}`,{level:"error"}),e}return"linux"===e&&V.isMuslEnvironment()?`linux-${t}-musl`:`${e}-${t}`}export function getBinaryName(e){return e.startsWith("win32")?"claude.exe":"claude"}function getBaseDirectories(){const e=getBinaryName(getPlatform());return{versions:$(J(),"claude","versions"),staging:$(X(),"claude","staging"),locks:$(Q(),"claude","locks"),executable:$(G(),e)}}async function isPossibleClaudeBinary(e){try{const i=await p(e);return!(!i.isFile()||0===i.size)&&(await a(e,t.X_OK),!0)}catch{return!1}}async function getVersionPaths(e){const t=getBaseDirectories(),a=[t.versions,t.staging,t.locks];await Promise.all(a.map(e=>n(e,{recursive:!0})));const i=v(t.executable);await n(i,{recursive:!0});const r=$(t.versions,e);try{await p(r)}catch{await w(r,"",{encoding:"utf8"})}return{stagingPath:$(t.staging,e),installPath:r}}async function tryWithVersionLock(e,t,a=0){const i=getBaseDirectories(),r=getLockFilePathFromVersionPath(i,e);if(await n(i.locks,{recursive:!0}),re()){let i=0;const o=a+1,n=a>0?1e3:100,s=a>0?5e3:500;for(;i<o;){if(await ne(e,r,async()=>{try{await t()}catch(e){throw q(e),e}}))return b("tengu_version_lock_acquired",{is_pid_based:!0,is_lifetime_lock:!1,attempts:i+1}),!0;if(i++,i<o){const e=Math.min(n*Math.pow(2,i-1),s);await Y(e)}}return b("tengu_version_lock_failed",{is_pid_based:!0,is_lifetime_lock:!1,attempts:o}),logLockAcquisitionError(e,new Error("Lock held by another process")),!1}let o=null;try{try{o=await O.lock(e,{stale:se,retries:{retries:a,minTimeout:a>0?1e3:100,maxTimeout:a>0?5e3:500},lockfilePath:r,onCompromised:e=>{L(`NON-FATAL: Version lock was compromised during operation: ${e.message}`,{level:"info"})}})}catch(t){return b("tengu_version_lock_failed",{is_pid_based:!1,is_lifetime_lock:!1}),logLockAcquisitionError(e,t),!1}try{return await t(),b("tengu_version_lock_acquired",{is_pid_based:!1,is_lifetime_lock:!1}),!0}catch(e){throw q(e),e}}finally{o&&await o()}}async function atomicMoveToInstallPath(e,t){await n(v(t),{recursive:!0});const a=`${t}.tmp.${process.pid}.${Date.now()}`;try{await r(e,a),await i(a,493),await u(a,t),L(`Atomically installed binary to ${t}`)}catch(e){try{await _(a)}catch{}throw e}}async function installVersion(e,t,a){"npm"===a?await async function(e,t){try{const a=$(e,"node_modules","@anthropic-ai"),i=(await s(a)).find(e=>e.startsWith("claude-cli-native-"));if(!i)throw b("tengu_native_install_package_failure",{stage_find_package:!0,error_package_not_found:!0}),new Error("Could not find platform-specific native package");const r=$(a,i,"cli");try{await p(r)}catch{throw b("tengu_native_install_package_failure",{stage_binary_exists:!0,error_binary_not_found:!0}),new Error("Native binary not found in staged package")}await atomicMoveToInstallPath(r,t),await d(e,{recursive:!0,force:!0}),b("tengu_native_install_package_success",{})}catch(e){const t=R(e);throw t.includes("Could not find platform-specific")||t.includes("Native binary not found")||b("tengu_native_install_package_failure",{stage_atomic_move:!0,error_move_failed:!0}),q(B(e)),e}}(e,t):await async function(e,t){try{const a=getBinaryName(getPlatform()),i=$(e,a);try{await p(i)}catch{throw b("tengu_native_install_binary_failure",{stage_binary_exists:!0,error_binary_not_found:!0}),new Error("Staged binary not found")}await atomicMoveToInstallPath(i,t),await d(e,{recursive:!0,force:!0}),b("tengu_native_install_binary_success",{})}catch(e){throw R(e).includes("Staged binary not found")||b("tengu_native_install_binary_failure",{stage_atomic_move:!0,error_move_failed:!0}),q(B(e)),e}}(e,t)}async function performVersionUpdate(e,t){const{stagingPath:a,installPath:i}=await getVersionPaths(e),{executable:o}=getBaseDirectories(),s=T(process.env.ENABLE_LOCKLESS_UPDATES)?`${a}.${process.pid}.${Date.now()}`:a,l=!await versionIsAvailable(e)||t;if(l){L(t?`Force reinstalling native installer version ${e}`:`Downloading native installer version ${e}`);const a=await Z(e,s);await installVersion(s,i,a)}else L(`Version ${e} already installed, updating symlink`);if(await removeDirectoryIfEmpty(o),await async function(e,t){const a=getPlatform();if(a.startsWith("win32"))try{const a=v(e);let i;await n(a,{recursive:!0});try{i=await p(e)}catch{}if(i){try{const e=await p(t);if(i.size===e.size)return!1}catch{}const a=`${e}.old.${Date.now()}`;await u(e,a);try{await r(t,e);try{await _(a)}catch{}}catch(t){try{await u(a,e)}catch(e){const a=new Error(`Failed to restore old executable: ${e}`,{cause:t});throw q(a),a}throw t}}else try{await r(t,e)}catch(e){if(S(e))throw new Error(`Source file does not exist: ${t}`);throw e}return!0}catch(a){return q(new Error(`Failed to copy executable from ${t} to ${e}: ${a}`)),!1}const i=v(e);try{await n(i,{recursive:!0}),L(`Created directory ${i} for symlink`)}catch(e){return q(new Error(`Failed to create directory ${i}: ${e}`)),!1}try{let a=!1;try{await p(e),a=!0}catch{}if(a){try{const a=await c(e),i=k(v(e),a);if(i===k(t))return!1}catch{}await _(e)}}catch(e){q(new Error(`Failed to check/remove existing symlink: ${e}`))}const o=`${e}.tmp.${process.pid}.${Date.now()}`;try{return await f(t,o),await u(o,e),L(`Atomically updated symlink ${e} -> ${t}`),!0}catch(a){try{await _(o)}catch{}return q(new Error(`Failed to create symlink from ${e} to ${t}: ${a}`)),!1}}(o,i),!await isPossibleClaudeBinary(o)){let e=!1;try{await p(i),e=!0}catch{}throw new Error(`Failed to create executable at ${o}. Source file exists: ${e}. Check write permissions to ${o}.`)}return l}async function versionIsAvailable(e){const{installPath:t}=await getVersionPaths(e);return isPossibleClaudeBinary(t)}async function updateLatest(t,a=!1){const i=Date.now();let r=await ee(t);const{executable:o}=getBaseDirectories();if(L(`Checking for native installer update to version ${r}`),!a){const t=await P();if(t&&M(r,t)){if(L(`Native installer: maxVersion ${t} is set, capping update from ${r} to ${t}`),U(e.VERSION,t))return L(`Native installer: current version ${e.VERSION} is already at or above maxVersion ${t}, skipping update`),b("tengu_native_update_skipped_max_version",{latency_ms:Date.now()-i,max_version:t,available_version:r}),{success:!0,latestVersion:r};r=t}}if(!a&&r===e.VERSION&&await versionIsAvailable(r)&&await isPossibleClaudeBinary(o))return L(`Found ${r} at ${o}, skipping install`),b("tengu_native_update_complete",{latency_ms:Date.now()-i,was_new_install:!1,was_force_reinstall:!1,was_already_running:!0}),{success:!0,latestVersion:r};if(!a&&x(r))return b("tengu_native_update_skipped_minimum_version",{latency_ms:Date.now()-i,target_version:r}),{success:!0,latestVersion:r};let n,s=!1;if(T(process.env.ENABLE_LOCKLESS_UPDATES))s=await performVersionUpdate(r,a),n=Date.now()-i;else{const{installPath:e}=await getVersionPaths(r);a&&await async function(e){const t=getLockFilePathFromVersionPath(getBaseDirectories(),e);try{await _(t),L(`Force-removed lock file at ${t}`)}catch(e){L(`Failed to force-remove lock file: ${R(e)}`)}}(e);const t=await tryWithVersionLock(e,async()=>{s=await performVersionUpdate(r,a)},3);if(n=Date.now()-i,!t){const t=getBaseDirectories();let a;if(re()){const i=getLockFilePathFromVersionPath(t,e);ie(i)&&(a=oe(i)?.pid)}return b("tengu_native_update_lock_failed",{latency_ms:n,lock_holder_pid:a}),{success:!1,latestVersion:r,lockFailed:!0,lockHolderPid:a}}}return b("tengu_native_update_complete",{latency_ms:n,was_new_install:s,was_force_reinstall:a}),L(`Successfully updated to version ${r}`),{success:!0,latestVersion:r}}export async function removeDirectoryIfEmpty(e){try{await m(e),L(`Removed empty directory at ${e}`)}catch(t){const a=D(t);"ENOTDIR"!==a&&"ENOENT"!==a&&"ENOTEMPTY"!==a&&L(`Could not remove directory at ${e}: ${t}`)}}export async function checkInstall(e=!1){if(T(process.env.DISABLE_INSTALLATION_CHECKS))return[];const t=await N();if("development"===t)return[];const i=A();if(!(e||"native"===t||"native"===i.installMethod))return[];const r=getBaseDirectories(),o=[],n=v(r.executable),s=k(n),l=getPlatform().startsWith("win32");try{await a(n)}catch{o.push({message:`installMethod is native, but directory ${n} does not exist`,userActionRequired:!0,type:"error"})}if(l)await isPossibleClaudeBinary(r.executable)||o.push({message:`installMethod is native, but claude command is missing or invalid at ${r.executable}`,userActionRequired:!0,type:"error"});else try{const e=await c(r.executable),t=k(v(r.executable),e);await isPossibleClaudeBinary(t)||o.push({message:`Claude symlink points to missing or invalid binary: ${e}`,userActionRequired:!0,type:"error"})}catch(e){S(e)?o.push({message:`installMethod is native, but claude command not found at ${r.executable}`,userActionRequired:!0,type:"error"}):await isPossibleClaudeBinary(r.executable)||o.push({message:`${r.executable} exists but is not a valid Claude binary`,userActionRequired:!0,type:"error"})}if(!(process.env.PATH||"").split(g).some(e=>{try{const t=k(e);return l?t.toLowerCase()===s.toLowerCase():t===s}catch{return!1}}))if(l){const e=n.replace(/\//g,"\\");o.push({message:`Native installation exists but ${e} is not in your PATH. Add it by opening: System Properties → Environment Variables → Edit User PATH → New → Add the path above. Then restart your terminal.`,userActionRequired:!0,type:"path"})}else{const e=j(),t=H()[e],a=t?t.replace(h(),"~"):"your shell config file";o.push({message:`Native installation exists but ~/.local/bin is not in your PATH. Run:\n\necho 'export PATH="$HOME/.local/bin:$PATH"' >> ${a} && source ${a}`,userActionRequired:!0,type:"path"})}return o}let ce=null;export function installLatest(e,t=!1){if(t)return installLatestImpl(e,t);if(ce)return L("installLatest: joining in-flight call"),ce;const a=installLatestImpl(e,t);ce=a;const clear=()=>{ce=null};return a.then(clear,clear),a}async function installLatestImpl(e,t=!1){const a=await updateLatest(e,t);if(!a.success)return{latestVersion:null,wasUpdated:!1,lockFailed:a.lockFailed,lockHolderPid:a.lockHolderPid};return"native"!==A().installMethod&&(F(e=>({...e,installMethod:"native",autoUpdates:!1,autoUpdatesProtectedForNative:!0})),L('Native installer: Set installMethod to "native" and disabled legacy auto-updater for protection')),cleanupOldVersions(),{latestVersion:a.latestVersion,wasUpdated:a.success,lockFailed:!1}}function getLockFilePathFromVersionPath(e,t){const a=y(t);return $(e.locks,`${a}.lock`)}export async function lockCurrentVersion(){const e=getBaseDirectories();if(!process.execPath.includes(e.versions))return;const t=k(process.execPath);try{const a=getLockFilePathFromVersionPath(e,t);if(await n(e.locks,{recursive:!0}),re()){if(!await te(t,a))return b("tengu_version_lock_failed",{is_pid_based:!0,is_lifetime_lock:!0}),void logLockAcquisitionError(t,new Error("Lock already held by another process"));b("tengu_version_lock_acquired",{is_pid_based:!0,is_lifetime_lock:!0}),L(`Acquired PID lock on running version: ${t}`)}else{let e;try{e=await O.lock(t,{stale:se,retries:0,lockfilePath:a,onCompromised:e=>{L(`NON-FATAL: Lock on running version was compromised: ${e.message}`,{level:"info"})}}),b("tengu_version_lock_acquired",{is_pid_based:!1,is_lifetime_lock:!0}),L(`Acquired mtime-based lock on running version: ${t}`),E(async()=>{try{await(e?.())}catch{}})}catch(e){return S(e)?void L(`Cannot lock current version - file does not exist: ${t}`,{level:"info"}):(b("tengu_version_lock_failed",{is_pid_based:!1,is_lifetime_lock:!0}),void logLockAcquisitionError(t,e))}}}catch(e){if(S(e))return void L(`Cannot lock current version - file does not exist: ${t}`,{level:"info"});L(`NON-FATAL: Failed to lock current version during execution ${R(e)}`,{level:"info"})}}function logLockAcquisitionError(e,t){q(new Error(`NON-FATAL: Lock acquisition failed for ${e} (expected in multi-process scenarios)`,{cause:t}))}export async function cleanupOldVersions(){await Promise.resolve();const e=getBaseDirectories(),t=Date.now()-36e5;if(getPlatform().startsWith("win32")){const t=v(e.executable);try{const e=await s(t);let a=0;for(const i of e)if(/^claude\.exe\.old\.\d+$/.test(i))try{await _($(t,i)),a++}catch{}a>0&&L(`Cleaned up ${a} old Windows executables on startup`)}catch(e){S(e)||L(`Failed to clean up old Windows executables: ${e}`)}}try{const a=await s(e.staging);let i=0;for(const r of a){const a=$(e.staging,r);try{(await p(a)).mtime.getTime()<t&&(await d(a,{recursive:!0,force:!0}),i++,L(`Cleaned up old staging directory: ${r}`))}catch{}}i>0&&(L(`Cleaned up ${i} orphaned staging directories`),b("tengu_native_staging_cleanup",{cleaned_count:i}))}catch(e){S(e)||L(`Failed to clean up staging directories: ${e}`)}if(re()){const t=ae(e.locks);t>0&&(L(`Cleaned up ${t} stale version locks`),b("tengu_native_stale_locks_cleanup",{cleaned_count:t}))}let a;try{a=await s(e.versions)}catch(e){return void(S(e)||L(`Failed to readdir versions directory: ${e}`))}const i=[];let r=0;for(const o of a){const a=$(e.versions,o);if(/\.tmp\.\d+\.\d+$/.test(o))try{(await p(a)).mtime.getTime()<t&&(await _(a),r++,L(`Cleaned up orphaned temp install file: ${o}`))}catch{}else try{const e=await p(a);if(!e.isFile())continue;if("win32"!==process.platform&&e.size>0&&!(73&e.mode))continue;i.push({name:o,path:a,resolvedPath:k(a),mtime:e.mtime})}catch{}}if(r>0&&(L(`Cleaned up ${r} orphaned temp install files`),b("tengu_native_temp_files_cleanup",{cleaned_count:r})),0!==i.length)try{const t=process.execPath,a=new Set;t&&t.includes(e.versions)&&a.add(k(t));const r=await async function(e){try{const t=await c(e),a=k(v(e),t);if(await isPossibleClaudeBinary(a))return a}catch{}return null}(e.executable);r&&a.add(r);for(const t of i){if(a.has(t.resolvedPath))continue;const i=getLockFilePathFromVersionPath(e,t.resolvedPath);let r=!1;if(re())r=ie(i);else try{r=await O.check(t.resolvedPath,{stale:se,lockfilePath:i})}catch{r=!1}r&&(a.add(t.resolvedPath),L(`Protecting locked version from cleanup: ${t.name}`))}const o=i.filter(e=>!a.has(e.resolvedPath)).sort((e,t)=>t.mtime.getTime()-e.mtime.getTime()).slice(2);if(0===o.length)return void b("tengu_native_version_cleanup",{total_count:i.length,deleted_count:0,protected_count:a.size,retained_count:2,lock_failed_count:0,error_count:0});let n=0,s=0,l=0;await Promise.all(o.map(async e=>{try{await tryWithVersionLock(e.path,async()=>{await _(e.path)})?n++:(s++,L(`Skipping deletion of ${e.name} - locked by another process`))}catch(t){l++,q(new Error(`Failed to delete version ${e.name}: ${t}`))}})),b("tengu_native_version_cleanup",{total_count:i.length,deleted_count:n,protected_count:a.size,retained_count:2,lock_failed_count:s,error_count:l})}catch(e){S(e)||q(new Error(`Version cleanup failed: ${e}`))}}export async function removeInstalledSymlink(){const e=getBaseDirectories();try{if(await async function(e){let t=e;return(await o(e)).isSymbolicLink()&&(t=await l(e)),t.endsWith(".js")||t.includes("node_modules")}(e.executable))return void L(`Skipping removal of ${e.executable} - appears to be npm-managed`);await _(e.executable),L(`Removed claude symlink at ${e.executable}`)}catch(e){if(S(e))return;q(new Error(`Failed to remove claude symlink: ${e}`))}}export async function cleanupShellAliases(){const e=[],t=H();for(const[a,i]of Object.entries(t))try{const t=await K(i);if(!t)continue;const{filtered:r,hadAlias:o}=W(t);o&&(await z(i,r),e.push({message:`Removed claude alias from ${i}. Run: unalias claude`,userActionRequired:!0,type:"alias"}),L(`Cleaned up claude alias from ${a} config`))}catch(t){q(t),e.push({message:`Failed to clean up ${i}: ${t}`,userActionRequired:!1,type:"error"})}return e}async function attemptNpmUninstall(e){const{code:t,stderr:a}=await I("npm",["uninstall","-g",e],{cwd:process.cwd()});if(0===t)return L(`Removed global npm installation of ${e}`),{success:!0};if(a&&!a.includes("npm ERR! code E404")){if(a.includes("npm error code ENOTEMPTY")){L(`Failed to uninstall global npm package ${e}: ${a}`,{level:"error"}),L("Attempting manual removal due to ENOTEMPTY error");const t=await async function(e){try{const t=await I("npm",["config","get","prefix"]);if(0!==t.code||!t.stdout)return{success:!1,error:"Failed to get npm global prefix"};const a=t.stdout.trim();let i=!1;async function tryRemove(e,t){try{return await _(e),L(`Manually removed ${t}: ${e}`),!0}catch{return!1}}if(getPlatform().startsWith("win32")){const r=$(a,"claude.cmd"),o=$(a,"claude.ps1"),n=$(a,"claude");await tryRemove(r,"bin script")&&(i=!0),await tryRemove(o,"PowerShell script")&&(i=!0),await tryRemove(n,"bin executable")&&(i=!0)}else{const s=$(a,"bin","claude");await tryRemove(s,"bin symlink")&&(i=!0)}return i?(L(`Successfully removed ${e} manually`),{success:!0,warning:`${e} executables removed, but node_modules directory was left intact for safety. You may manually delete it later at: ${getPlatform().startsWith("win32")?$(a,"node_modules",e):$(a,"lib","node_modules",e)}`}):{success:!1}}catch(c){return L(`Manual removal failed: ${c}`,{level:"error"}),{success:!1,error:`Manual removal failed: ${c}`}}}(e);if(t.success)return{success:!0,warning:t.warning};if(t.error)return{success:!1,error:`Failed to remove global npm installation of ${e}: ${a}. Manual removal also failed: ${t.error}`}}return L(`Failed to uninstall global npm package ${e}: ${a}`,{level:"error"}),{success:!1,error:`Failed to remove global npm installation of ${e}: ${a}`}}return{success:!1}}export async function cleanupNpmInstallations(){const t=[],a=[];let i=0;const r=await attemptNpmUninstall("@anthropic-ai/claude-code");if(r.success?(i++,r.warning&&a.push(r.warning)):r.error&&t.push(r.error),e.PACKAGE_URL&&"@anthropic-ai/claude-code"!==e.PACKAGE_URL){const r=await attemptNpmUninstall(e.PACKAGE_URL);r.success?(i++,r.warning&&a.push(r.warning)):r.error&&t.push(r.error)}const o=$(h(),".context","local");try{await d(o,{recursive:!0}),i++,L(`Removed local installation at ${o}`)}catch(e){S(e)||(t.push(`Failed to remove ${o}: ${e}`),L(`Failed to remove local installation: ${e}`,{level:"error"}))}return{removed:i,errors:t,warnings:a}}
1
+ import{MACRO as e}from"../../recovery/bunBundleShim.js";import{constants as t}from"fs";import{access as a,chmod as i,copyFile as r,lstat as o,mkdir as n,readdir as s,readlink as c,realpath as l,rename as u,rm as d,rmdir as m,stat as p,symlink as f,unlink as _,writeFile as w}from"fs/promises";import{homedir as h}from"os";import{basename as y,delimiter as g,dirname as v,join as $,resolve as k}from"path";import{logEvent as b}from"../../services/analytics/index.js";import{getMaxVersion as P,shouldSkipVersion as x}from"../autoUpdater.js";import{registerCleanup as E}from"../cleanupRegistry.js";import{getGlobalConfig as A,saveGlobalConfig as F}from"../config.js";import{logForDebugging as L}from"../debug.js";import{getCurrentInstallationType as N}from"../doctorDiagnostic.js";import{env as C}from"../env.js";import{envDynamic as V}from"../envDynamic.js";import{isEnvTruthy as T}from"../envUtils.js";import{errorMessage as R,getErrnoCode as D,isENOENT as S,toError as B}from"../errors.js";import{execFileNoThrowWithCwd as I}from"../execFileNoThrow.js";import{getShellType as j}from"../localInstaller.js";import*as O from"../lockfile.js";import{logError as q}from"../log.js";import{gt as M,gte as U}from"../semver.js";import{filterClaudeAliases as W,getShellConfigPaths as H,readFileLines as K,writeFileLines as z}from"../shellConfig.js";import{sleep as Y}from"../sleep.js";import{getUserBinDir as G,getXDGCacheHome as X,getXDGDataHome as J,getXDGStateHome as Q}from"../xdg.js";import{downloadVersion as Z,getLatestVersion as ee}from"./download.js";import{acquireProcessLifetimeLock as te,cleanupStaleLocks as ae,isLockActive as ie,isPidBasedLockingEnabled as re,readLockContent as oe,withLock as ne}from"./pidLock.js";export const VERSION_RETENTION_COUNT=2;const se=6048e5;export function getPlatform(){const e=C.platform,t="x64"===process.arch?"x64":"arm64"===process.arch?"arm64":null;if(!t){const e=new Error(`Unsupported architecture: ${process.arch}`);throw L(`Native installer does not support architecture: ${process.arch}`,{level:"error"}),e}return"linux"===e&&V.isMuslEnvironment()?`linux-${t}-musl`:`${e}-${t}`}export function getBinaryName(e){return e.startsWith("win32")?"claude.exe":"claude"}function getBaseDirectories(){const e=getBinaryName(getPlatform());return{versions:$(J(),"claude","versions"),staging:$(X(),"claude","staging"),locks:$(Q(),"claude","locks"),executable:$(G(),e)}}async function isPossibleClaudeBinary(e){try{const i=await p(e);return!(!i.isFile()||0===i.size)&&(await a(e,t.X_OK),!0)}catch{return!1}}async function getVersionPaths(e){const t=getBaseDirectories(),a=[t.versions,t.staging,t.locks];await Promise.all(a.map(e=>n(e,{recursive:!0})));const i=v(t.executable);await n(i,{recursive:!0});const r=$(t.versions,e);try{await p(r)}catch{await w(r,"",{encoding:"utf8"})}return{stagingPath:$(t.staging,e),installPath:r}}async function tryWithVersionLock(e,t,a=0){const i=getBaseDirectories(),r=getLockFilePathFromVersionPath(i,e);if(await n(i.locks,{recursive:!0}),re()){let i=0;const o=a+1,n=a>0?1e3:100,s=a>0?5e3:500;for(;i<o;){if(await ne(e,r,async()=>{try{await t()}catch(e){throw q(e),e}}))return b("tengu_version_lock_acquired",{is_pid_based:!0,is_lifetime_lock:!1,attempts:i+1}),!0;if(i++,i<o){const e=Math.min(n*Math.pow(2,i-1),s);await Y(e)}}return b("tengu_version_lock_failed",{is_pid_based:!0,is_lifetime_lock:!1,attempts:o}),logLockAcquisitionError(e,new Error("Lock held by another process")),!1}let o=null;try{try{o=await O.lock(e,{stale:se,retries:{retries:a,minTimeout:a>0?1e3:100,maxTimeout:a>0?5e3:500},lockfilePath:r,onCompromised:e=>{L(`NON-FATAL: Version lock was compromised during operation: ${e.message}`,{level:"info"})}})}catch(t){return b("tengu_version_lock_failed",{is_pid_based:!1,is_lifetime_lock:!1}),logLockAcquisitionError(e,t),!1}try{return await t(),b("tengu_version_lock_acquired",{is_pid_based:!1,is_lifetime_lock:!1}),!0}catch(e){throw q(e),e}}finally{o&&await o()}}async function atomicMoveToInstallPath(e,t){await n(v(t),{recursive:!0});const a=`${t}.tmp.${process.pid}.${Date.now()}`;try{await r(e,a),await i(a,493),await u(a,t),L(`Atomically installed binary to ${t}`)}catch(e){try{await _(a)}catch{}throw e}}async function installVersion(e,t,a){"npm"===a?await async function(e,t){try{const a=$(e,"node_modules","@anthropic-ai"),i=(await s(a)).find(e=>e.startsWith("claude-cli-native-"));if(!i)throw b("tengu_native_install_package_failure",{stage_find_package:!0,error_package_not_found:!0}),new Error("Could not find platform-specific native package");const r=$(a,i,"cli");try{await p(r)}catch{throw b("tengu_native_install_package_failure",{stage_binary_exists:!0,error_binary_not_found:!0}),new Error("Native binary not found in staged package")}await atomicMoveToInstallPath(r,t),await d(e,{recursive:!0,force:!0}),b("tengu_native_install_package_success",{})}catch(e){const t=R(e);throw t.includes("Could not find platform-specific")||t.includes("Native binary not found")||b("tengu_native_install_package_failure",{stage_atomic_move:!0,error_move_failed:!0}),q(B(e)),e}}(e,t):await async function(e,t){try{const a=getBinaryName(getPlatform()),i=$(e,a);try{await p(i)}catch{throw b("tengu_native_install_binary_failure",{stage_binary_exists:!0,error_binary_not_found:!0}),new Error("Staged binary not found")}await atomicMoveToInstallPath(i,t),await d(e,{recursive:!0,force:!0}),b("tengu_native_install_binary_success",{})}catch(e){throw R(e).includes("Staged binary not found")||b("tengu_native_install_binary_failure",{stage_atomic_move:!0,error_move_failed:!0}),q(B(e)),e}}(e,t)}async function performVersionUpdate(e,t){const{stagingPath:a,installPath:i}=await getVersionPaths(e),{executable:o}=getBaseDirectories(),s=T(process.env.ENABLE_LOCKLESS_UPDATES)?`${a}.${process.pid}.${Date.now()}`:a,l=!await versionIsAvailable(e)||t;if(l){L(t?`Force reinstalling native installer version ${e}`:`Downloading native installer version ${e}`);const a=await Z(e,s);await installVersion(s,i,a)}else L(`Version ${e} already installed, updating symlink`);if(await removeDirectoryIfEmpty(o),await async function(e,t){const a=getPlatform();if(a.startsWith("win32"))try{const a=v(e);let i;await n(a,{recursive:!0});try{i=await p(e)}catch{}if(i){try{const e=await p(t);if(i.size===e.size)return!1}catch{}const a=`${e}.old.${Date.now()}`;await u(e,a);try{await r(t,e);try{await _(a)}catch{}}catch(t){try{await u(a,e)}catch(e){const a=new Error(`Failed to restore old executable: ${e}`,{cause:t});throw q(a),a}throw t}}else try{await r(t,e)}catch(e){if(S(e))throw new Error(`Source file does not exist: ${t}`);throw e}return!0}catch(a){return q(new Error(`Failed to copy executable from ${t} to ${e}: ${a}`)),!1}const i=v(e);try{await n(i,{recursive:!0}),L(`Created directory ${i} for symlink`)}catch(e){return q(new Error(`Failed to create directory ${i}: ${e}`)),!1}try{let a=!1;try{await p(e),a=!0}catch{}if(a){try{const a=await c(e),i=k(v(e),a);if(i===k(t))return!1}catch{}await _(e)}}catch(e){q(new Error(`Failed to check/remove existing symlink: ${e}`))}const o=`${e}.tmp.${process.pid}.${Date.now()}`;try{return await f(t,o),await u(o,e),L(`Atomically updated symlink ${e} -> ${t}`),!0}catch(a){try{await _(o)}catch{}return q(new Error(`Failed to create symlink from ${e} to ${t}: ${a}`)),!1}}(o,i),!await isPossibleClaudeBinary(o)){let e=!1;try{await p(i),e=!0}catch{}throw new Error(`Failed to create executable at ${o}. Source file exists: ${e}. Check write permissions to ${o}.`)}return l}async function versionIsAvailable(e){const{installPath:t}=await getVersionPaths(e);return isPossibleClaudeBinary(t)}async function updateLatest(t,a=!1){const i=Date.now();let r=await ee(t);const{executable:o}=getBaseDirectories();if(L(`Checking for native installer update to version ${r}`),!a){const t=await P();if(t&&M(r,t)){if(L(`Native installer: maxVersion ${t} is set, capping update from ${r} to ${t}`),U(e.VERSION,t))return L(`Native installer: current version ${e.VERSION} is already at or above maxVersion ${t}, skipping update`),b("tengu_native_update_skipped_max_version",{latency_ms:Date.now()-i,max_version:t,available_version:r}),{success:!0,latestVersion:r};r=t}}if(!a&&r===e.VERSION&&await versionIsAvailable(r)&&await isPossibleClaudeBinary(o))return L(`Found ${r} at ${o}, skipping install`),b("tengu_native_update_complete",{latency_ms:Date.now()-i,was_new_install:!1,was_force_reinstall:!1,was_already_running:!0}),{success:!0,latestVersion:r};if(!a&&x(r))return b("tengu_native_update_skipped_minimum_version",{latency_ms:Date.now()-i,target_version:r}),{success:!0,latestVersion:r};let n,s=!1;if(T(process.env.ENABLE_LOCKLESS_UPDATES))s=await performVersionUpdate(r,a),n=Date.now()-i;else{const{installPath:e}=await getVersionPaths(r);a&&await async function(e){const t=getLockFilePathFromVersionPath(getBaseDirectories(),e);try{await _(t),L(`Force-removed lock file at ${t}`)}catch(e){L(`Failed to force-remove lock file: ${R(e)}`)}}(e);const t=await tryWithVersionLock(e,async()=>{s=await performVersionUpdate(r,a)},3);if(n=Date.now()-i,!t){const t=getBaseDirectories();let a;if(re()){const i=getLockFilePathFromVersionPath(t,e);ie(i)&&(a=oe(i)?.pid)}return b("tengu_native_update_lock_failed",{latency_ms:n,lock_holder_pid:a}),{success:!1,latestVersion:r,lockFailed:!0,lockHolderPid:a}}}return b("tengu_native_update_complete",{latency_ms:n,was_new_install:s,was_force_reinstall:a}),L(`Successfully updated to version ${r}`),{success:!0,latestVersion:r}}export async function removeDirectoryIfEmpty(e){try{await m(e),L(`Removed empty directory at ${e}`)}catch(t){const a=D(t);"ENOTDIR"!==a&&"ENOENT"!==a&&"ENOTEMPTY"!==a&&L(`Could not remove directory at ${e}: ${t}`)}}export async function checkInstall(e=!1){if(T(process.env.DISABLE_INSTALLATION_CHECKS))return[];const t=await N();if("development"===t)return[];const i=A();if(!(e||"native"===t||"native"===i.installMethod))return[];const r=getBaseDirectories(),o=[],n=v(r.executable),s=k(n),l=getPlatform().startsWith("win32");try{await a(n)}catch{o.push({message:`installMethod is native, but directory ${n} does not exist`,userActionRequired:!0,type:"error"})}if(l)await isPossibleClaudeBinary(r.executable)||o.push({message:`installMethod is native, but claude command is missing or invalid at ${r.executable}`,userActionRequired:!0,type:"error"});else try{const e=await c(r.executable),t=k(v(r.executable),e);await isPossibleClaudeBinary(t)||o.push({message:`Claude symlink points to missing or invalid binary: ${e}`,userActionRequired:!0,type:"error"})}catch(e){S(e)?o.push({message:`installMethod is native, but claude command not found at ${r.executable}`,userActionRequired:!0,type:"error"}):await isPossibleClaudeBinary(r.executable)||o.push({message:`${r.executable} exists but is not a valid Claude binary`,userActionRequired:!0,type:"error"})}if(!(process.env.PATH||"").split(g).some(e=>{try{const t=k(e);return l?t.toLowerCase()===s.toLowerCase():t===s}catch{return!1}}))if(l){const e=n.replace(/\//g,"\\");o.push({message:`Native installation exists but ${e} is not in your PATH. Add it by opening: System Properties → Environment Variables → Edit User PATH → New → Add the path above. Then restart your terminal.`,userActionRequired:!0,type:"path"})}else{const e=j(),t=H()[e],a=t?t.replace(h(),"~"):"your shell config file";o.push({message:`Native installation exists but ~/.local/bin is not in your PATH. Run:\n\necho 'export PATH="$HOME/.local/bin:$PATH"' >> ${a} && source ${a}`,userActionRequired:!0,type:"path"})}return o}let ce=null;export function installLatest(e,t=!1){if(t)return installLatestImpl(e,t);if(ce)return L("installLatest: joining in-flight call"),ce;const a=installLatestImpl(e,t);ce=a;const clear=()=>{ce=null};return a.then(clear,clear),a}async function installLatestImpl(e,t=!1){const a=await updateLatest(e,t);if(!a.success)return{latestVersion:null,wasUpdated:!1,lockFailed:a.lockFailed,lockHolderPid:a.lockHolderPid};return"native"!==A().installMethod&&(F(e=>({...e,installMethod:"native",autoUpdates:!1,autoUpdatesProtectedForNative:!0})),L('Native installer: Set installMethod to "native" and disabled legacy auto-updater for protection')),cleanupOldVersions(),{latestVersion:a.latestVersion,wasUpdated:a.success,lockFailed:!1}}function getLockFilePathFromVersionPath(e,t){const a=y(t);return $(e.locks,`${a}.lock`)}export async function lockCurrentVersion(){const e=getBaseDirectories();if(!process.execPath.includes(e.versions))return;const t=k(process.execPath);try{const a=getLockFilePathFromVersionPath(e,t);if(await n(e.locks,{recursive:!0}),re()){if(!await te(t,a))return b("tengu_version_lock_failed",{is_pid_based:!0,is_lifetime_lock:!0}),void logLockAcquisitionError(t,new Error("Lock already held by another process"));b("tengu_version_lock_acquired",{is_pid_based:!0,is_lifetime_lock:!0}),L(`Acquired PID lock on running version: ${t}`)}else{let e;try{e=await O.lock(t,{stale:se,retries:0,lockfilePath:a,onCompromised:e=>{L(`NON-FATAL: Lock on running version was compromised: ${e.message}`,{level:"info"})}}),b("tengu_version_lock_acquired",{is_pid_based:!1,is_lifetime_lock:!0}),L(`Acquired mtime-based lock on running version: ${t}`),E(async()=>{try{await(e?.())}catch{}})}catch(e){return S(e)?void L(`Cannot lock current version - file does not exist: ${t}`,{level:"info"}):(b("tengu_version_lock_failed",{is_pid_based:!1,is_lifetime_lock:!0}),void logLockAcquisitionError(t,e))}}}catch(e){if(S(e))return void L(`Cannot lock current version - file does not exist: ${t}`,{level:"info"});L(`NON-FATAL: Failed to lock current version during execution ${R(e)}`,{level:"info"})}}function logLockAcquisitionError(e,t){q(new Error(`NON-FATAL: Lock acquisition failed for ${e} (expected in multi-process scenarios)`,{cause:t}))}export async function cleanupOldVersions(){await Promise.resolve();const e=getBaseDirectories(),t=Date.now()-36e5;if(getPlatform().startsWith("win32")){const t=v(e.executable);try{const e=await s(t);let a=0;for(const i of e)if(/^claude\.exe\.old\.\d+$/.test(i))try{await _($(t,i)),a++}catch{}a>0&&L(`Cleaned up ${a} old Windows executables on startup`)}catch(e){S(e)||L(`Failed to clean up old Windows executables: ${e}`)}}try{const a=await s(e.staging);let i=0;for(const r of a){const a=$(e.staging,r);try{(await p(a)).mtime.getTime()<t&&(await d(a,{recursive:!0,force:!0}),i++,L(`Cleaned up old staging directory: ${r}`))}catch{}}i>0&&(L(`Cleaned up ${i} orphaned staging directories`),b("tengu_native_staging_cleanup",{cleaned_count:i}))}catch(e){S(e)||L(`Failed to clean up staging directories: ${e}`)}if(re()){const t=ae(e.locks);t>0&&(L(`Cleaned up ${t} stale version locks`),b("tengu_native_stale_locks_cleanup",{cleaned_count:t}))}let a;try{a=await s(e.versions)}catch(e){return void(S(e)||L(`Failed to readdir versions directory: ${e}`))}const i=[];let r=0;for(const o of a){const a=$(e.versions,o);if(/\.tmp\.\d+\.\d+$/.test(o))try{(await p(a)).mtime.getTime()<t&&(await _(a),r++,L(`Cleaned up orphaned temp install file: ${o}`))}catch{}else try{const e=await p(a);if(!e.isFile())continue;if("win32"!==process.platform&&e.size>0&&!(73&e.mode))continue;i.push({name:o,path:a,resolvedPath:k(a),mtime:e.mtime})}catch{}}if(r>0&&(L(`Cleaned up ${r} orphaned temp install files`),b("tengu_native_temp_files_cleanup",{cleaned_count:r})),0!==i.length)try{const t=process.execPath,a=new Set;t&&t.includes(e.versions)&&a.add(k(t));const r=await async function(e){try{const t=await c(e),a=k(v(e),t);if(await isPossibleClaudeBinary(a))return a}catch{}return null}(e.executable);r&&a.add(r);for(const t of i){if(a.has(t.resolvedPath))continue;const i=getLockFilePathFromVersionPath(e,t.resolvedPath);let r=!1;if(re())r=ie(i);else try{r=await O.check(t.resolvedPath,{stale:se,lockfilePath:i})}catch{r=!1}r&&(a.add(t.resolvedPath),L(`Protecting locked version from cleanup: ${t.name}`))}const o=i.filter(e=>!a.has(e.resolvedPath)).sort((e,t)=>t.mtime.getTime()-e.mtime.getTime()).slice(2);if(0===o.length)return void b("tengu_native_version_cleanup",{total_count:i.length,deleted_count:0,protected_count:a.size,retained_count:2,lock_failed_count:0,error_count:0});let n=0,s=0,l=0;await Promise.all(o.map(async e=>{try{await tryWithVersionLock(e.path,async()=>{await _(e.path)})?n++:(s++,L(`Skipping deletion of ${e.name} - locked by another process`))}catch(t){l++,q(new Error(`Failed to delete version ${e.name}: ${t}`))}})),b("tengu_native_version_cleanup",{total_count:i.length,deleted_count:n,protected_count:a.size,retained_count:2,lock_failed_count:s,error_count:l})}catch(e){S(e)||q(new Error(`Version cleanup failed: ${e}`))}}export async function removeInstalledSymlink(){const e=getBaseDirectories();try{if(await async function(e){let t=e;return(await o(e)).isSymbolicLink()&&(t=await l(e)),t.endsWith(".js")||t.includes("node_modules")}(e.executable))return void L(`Skipping removal of ${e.executable} - appears to be npm-managed`);await _(e.executable),L(`Removed claude symlink at ${e.executable}`)}catch(e){if(S(e))return;q(new Error(`Failed to remove claude symlink: ${e}`))}}export async function cleanupShellAliases(){const e=[],t=H();for(const[a,i]of Object.entries(t))try{const t=await K(i);if(!t)continue;const{filtered:r,hadAlias:o}=W(t);o&&(await z(i,r),e.push({message:`Removed claude alias from ${i}. Run: unalias claude`,userActionRequired:!0,type:"alias"}),L(`Cleaned up claude alias from ${a} config`))}catch(t){q(t),e.push({message:`Failed to clean up ${i}: ${t}`,userActionRequired:!1,type:"error"})}return e}async function attemptNpmUninstall(e){const{code:t,stderr:a}=await I("npm",["uninstall","-g",e],{cwd:process.cwd()});if(0===t)return L(`Removed global npm installation of ${e}`),{success:!0};if(a&&!a.includes("npm ERR! code E404")){if(a.includes("npm error code ENOTEMPTY")){L(`Failed to uninstall global npm package ${e}: ${a}`,{level:"error"}),L("Attempting manual removal due to ENOTEMPTY error");const t=await async function(e){try{const t=await I("npm",["config","get","prefix"]);if(0!==t.code||!t.stdout)return{success:!1,error:"Failed to get npm global prefix"};const a=t.stdout.trim();let i=!1;async function tryRemove(e,t){try{return await _(e),L(`Manually removed ${t}: ${e}`),!0}catch{return!1}}if(getPlatform().startsWith("win32")){const r=$(a,"claude.cmd"),o=$(a,"claude.ps1"),n=$(a,"claude");await tryRemove(r,"bin script")&&(i=!0),await tryRemove(o,"PowerShell script")&&(i=!0),await tryRemove(n,"bin executable")&&(i=!0)}else{const s=$(a,"bin","claude");await tryRemove(s,"bin symlink")&&(i=!0)}return i?(L(`Successfully removed ${e} manually`),{success:!0,warning:`${e} executables removed, but node_modules directory was left intact for safety. You may manually delete it later at: ${getPlatform().startsWith("win32")?$(a,"node_modules",e):$(a,"lib","node_modules",e)}`}):{success:!1}}catch(c){return L(`Manual removal failed: ${c}`,{level:"error"}),{success:!1,error:`Manual removal failed: ${c}`}}}(e);if(t.success)return{success:!0,warning:t.warning};if(t.error)return{success:!1,error:`Failed to remove global npm installation of ${e}: ${a}. Manual removal also failed: ${t.error}`}}return L(`Failed to uninstall global npm package ${e}: ${a}`,{level:"error"}),{success:!1,error:`Failed to remove global npm installation of ${e}: ${a}`}}return{success:!1}}export async function cleanupNpmInstallations(){const t=[],a=[];let i=0;const r=await attemptNpmUninstall("@anthropic-ai/claude-code");if(r.success?(i++,r.warning&&a.push(r.warning)):r.error&&t.push(r.error),e.PACKAGE_URL&&"@anthropic-ai/claude-code"!==e.PACKAGE_URL){const r=await attemptNpmUninstall(e.PACKAGE_URL);r.success?(i++,r.warning&&a.push(r.warning)):r.error&&t.push(r.error)}const o=$(h(),".contextcli","local");try{await d(o,{recursive:!0}),i++,L(`Removed local installation at ${o}`)}catch(e){S(e)||(t.push(`Failed to remove ${o}: ${e}`),L(`Failed to remove local installation: ${e}`,{level:"error"}))}return{removed:i,errors:t,warnings:a}}
@@ -1 +1 @@
1
- import{MACRO as e,feature as t}from"../../recovery/bunBundleShim.js";import{randomBytes as o}from"crypto";import r from"ignore";import s from"lodash-es/memoize.js";import{homedir as n,tmpdir as i}from"os";import{join as a,normalize as l,posix as c,sep as u}from"path";import{hasAutoMemPathOverride as p,isAutoMemPath as d}from"../../memdir/paths.js";import{isAgentMemoryPath as h}from"../../tools/AgentTool/agentMemory.js";import{CLAUDE_FOLDER_PERMISSION_PATTERN as f,FILE_EDIT_TOOL_NAME as m,GLOBAL_CLAUDE_FOLDER_PERMISSION_PATTERN as g}from"../../tools/FileEditTool/constants.js";import{getOriginalCwd as w,getSessionId as P}from"../../bootstrap/state.js";import{checkStatsigFeatureGate_CACHED_MAY_BE_STALE as v}from"../../services/analytics/growthbook.js";import{FILE_READ_TOOL_NAME as y}from"../../tools/FileReadTool/prompt.js";import{getCwd as C}from"../cwd.js";import{getClaudeConfigHomeDir as R}from"../envUtils.js";import{getFsImplementation as b,getPathsForPermissionCheck as k}from"../fsOperations.js";import{containsPathTraversal as S,expandPath as W,getDirectoryForPath as x,sanitizePath as I}from"../path.js";import{getPlanSlug as D,getPlansDirectory as F}from"../plans.js";import{getPlatform as j}from"../platform.js";import{getProjectDir as $}from"../sessionStorage.js";import{SETTING_SOURCES as E}from"../settings/constants.js";import{getSettingsFilePathForSource as T,getSettingsRootPathForSource as A}from"../settings/settings.js";import{containsVulnerableUncPath as z}from"../shell/readOnlyCommandValidation.js";import{getToolResultsDir as O}from"../toolResultStorage.js";import{windowsPathToPosixPath as N}from"../windowsPaths.js";import{createReadRuleSuggestion as M}from"./PermissionUpdate.js";import{getRuleByContentsForToolName as _}from"./permissions.js";export const DANGEROUS_FILES=[".gitconfig",".gitmodules",".bashrc",".bash_profile",".zshrc",".zprofile",".profile",".ripgreprc",".mcp.json",".claude.json",".context.json"];export const DANGEROUS_DIRECTORIES=[".git",".vscode",".idea",".claude"];export function normalizeCaseForComparison(e){return e.toLowerCase()}export function getClaudeSkillScope(e){const t=W(e),o=normalizeCaseForComparison(t),r=[{dir:W(a(w(),".context","skills")),prefix:"/.context/skills/"},{dir:W(a(R(),"skills")),prefix:"~/.context/skills/"},{dir:W(a(w(),".context","skills")),prefix:"/.context/skills/"},{dir:W(a(n(),".context","skills")),prefix:"~/.context/skills/"}];for(const{dir:e,prefix:s}of r){const r=normalizeCaseForComparison(e);for(const n of[u,"/"])if(o.startsWith(r+n.toLowerCase())){const o=t.slice(e.length+n.length),r=o.indexOf("/"),i="\\"===u?o.indexOf("\\"):-1,a=-1===r?i:-1===i?r:Math.min(r,i);if(a<=0)return null;const l=o.slice(0,a);return!l||"."===l||l.includes("..")||/[*?[\]]/.test(l)?null:{skillName:l,pattern:s+l+"/**"}}}return null}const U=c.sep;export function relativePath(e,t){if("windows"===j()){const o=N(e),r=N(t);return c.relative(o,r)}return c.relative(e,t)}export function toPosixPath(e){return"windows"===j()?N(e):e}export function isClaudeSettingsPath(e){const t=normalizeCaseForComparison(W(e));return!(!t.endsWith(`${u}.context${u}settings.json`)&&!t.endsWith(`${u}.context${u}settings.local.json`))||E.map(e=>T(e)).filter(e=>void 0!==e).some(e=>normalizeCaseForComparison(e)===t)}function isClaudeConfigFilePath(e){if(isClaudeSettingsPath(e))return!0;const t=a(w(),".claude","commands"),o=a(w(),".claude","agents"),r=a(w(),".claude","skills"),s=a(w(),".context","commands"),n=a(w(),".context","agents"),i=a(w(),".context","skills");return pathInWorkingPath(e,t)||pathInWorkingPath(e,o)||pathInWorkingPath(e,r)||pathInWorkingPath(e,s)||pathInWorkingPath(e,n)||pathInWorkingPath(e,i)}function isSessionPlanFile(e){const t=a(F(),D()),o=l(e);return o.startsWith(t)&&o.endsWith(".md")}export function getSessionMemoryDir(){return a($(C()),P(),"session-memory")+u}export function getSessionMemoryPath(){return a(getSessionMemoryDir(),"summary.md")}export function isScratchpadEnabled(){return v("tengu_scratch")}export function getClaudeTempDirName(){if("windows"===j())return"claude";return`claude-${process.getuid?.()??0}`}export const getClaudeTempDir=s(function(){const e=process.env.CONTEXT_CODE_TMPDIR||process.env.CLAUDE_CODE_TMPDIR||("windows"===j()?i():"/tmp"),t=b();let o=e;try{o=t.realpathSync(e)}catch{}return a(o,getClaudeTempDirName())+u});export const getBundledSkillsRoot=s(function(){const t=o(16).toString("hex");return a(getClaudeTempDir(),"bundled-skills",e.VERSION,t)});export function getProjectTempDir(){return a(getClaudeTempDir(),I(w()))+u}export function getScratchpadDir(){return a(getProjectTempDir(),P(),"scratchpad")}export async function ensureScratchpadDir(){if(!isScratchpadEnabled())throw new Error("Scratchpad directory feature is not enabled");const e=b(),t=getScratchpadDir();return await e.mkdir(t,{mode:448}),t}function isScratchpadPath(e){if(!isScratchpadEnabled())return!1;const t=getScratchpadDir(),o=l(e);return o===t||o.startsWith(t+u)}function isDangerousFilePathToAutoEdit(e){const t=W(e).split(u),o=t.at(-1);if(e.startsWith("\\\\")||e.startsWith("//"))return!0;for(let e=0;e<t.length;e++){const o=normalizeCaseForComparison(t[e]);for(const r of DANGEROUS_DIRECTORIES)if(o===normalizeCaseForComparison(r)){if(".claude"===r){const o=t[e+1];if(o&&"worktrees"===normalizeCaseForComparison(o))break}return!0}}if(o){const e=normalizeCaseForComparison(o);if(DANGEROUS_FILES.some(t=>normalizeCaseForComparison(t)===e))return!0}return!1}function hasSuspiciousWindowsPathPattern(e){if("windows"===j()||"wsl"===j()){if(-1!==e.indexOf(":",2))return!0}return!!/~\d/.test(e)||(!!(e.startsWith("\\\\?\\")||e.startsWith("\\\\.\\")||e.startsWith("//?/")||e.startsWith("//./"))||(!!/[.\s]+$/.test(e)||(!!/\.(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(e)||(!!/(^|\/|\\)\.{3,}(\/|\\|$)/.test(e)||!!z(e)))))}export function checkPathSafetyForAutoEdit(e,t){const o=t??k(e);for(const t of o)if(hasSuspiciousWindowsPathPattern(t))return{safe:!1,message:`Claude requested permissions to write to ${e}, which contains a suspicious Windows path pattern that requires manual approval.`,classifierApprovable:!1};for(const t of o)if(isClaudeConfigFilePath(t))return{safe:!1,message:`Context solicitó permisos para escribir en ${e}, pero aún no se los has otorgado.`,classifierApprovable:!0};for(const t of o)if(isDangerousFilePathToAutoEdit(t))return{safe:!1,message:`Claude requested permissions to edit ${e} which is a sensitive file.`,classifierApprovable:!0};return{safe:!0}}export function allWorkingDirectories(e){return new Set([w(),...e.additionalWorkingDirectories.keys()])}export const getResolvedWorkingDirPaths=s(k);export function pathInAllowedWorkingPath(e,t,o){const r=o??k(e),s=Array.from(allWorkingDirectories(t)).flatMap(e=>getResolvedWorkingDirPaths(e));return r.every(e=>s.some(t=>pathInWorkingPath(e,t)))}export function pathInWorkingPath(e,t){const o=W(e),r=W(t),s=o.replace(/^\/private\/var\//,"/var/").replace(/^\/private\/tmp(\/|$)/,"/tmp$1"),n=r.replace(/^\/private\/var\//,"/var/").replace(/^\/private\/tmp(\/|$)/,"/tmp$1"),i=normalizeCaseForComparison(s),a=relativePath(normalizeCaseForComparison(n),i);return""===a||!S(a)&&!c.isAbsolute(a)}function rootPathForSource(e){switch(e){case"cliArg":case"command":case"session":return W(w());case"userSettings":case"policySettings":case"projectSettings":case"localSettings":case"flagSettings":return A(e)}}function prependDirSep(e){return c.join(U,e)}function normalizePatternToPath({patternRoot:e,pattern:t,rootPath:o}){const r=c.join(e,t);if(e===o)return prependDirSep(t);if(r.startsWith(`${o}${U}`)){return prependDirSep(r.slice(o.length))}{const r=c.relative(o,e);if(!r||r.startsWith(`..${U}`)||".."===r)return null;return prependDirSep(c.join(r,t))}}export function normalizePatternsToPath(e,t){const o=new Set(e.get(null)??[]);for(const[r,s]of e.entries())if(null!==r)for(const e of s){const s=normalizePatternToPath({patternRoot:r,pattern:e,rootPath:t});s&&o.add(s)}return Array.from(o)}export function getFileReadIgnorePatterns(e){const t=getPatternsByRoot(e,"read","deny"),o=new Map;for(const[e,r]of t.entries())o.set(e,Array.from(r.keys()));return o}function patternWithRoot(e,t){if(e.startsWith(`${U}${U}`)){const t=e.slice(1);if("windows"===j()&&t.match(/^\/[a-z]\//i)){const e=t[1]?.toUpperCase()??"C",o=t.slice(2),r=`${e}:\\`;return{relativePattern:o.startsWith("/")?o.slice(1):o,root:r}}return{relativePattern:t,root:U}}if(e.startsWith(`~${U}`))return{relativePattern:e.slice(1),root:n().normalize("NFC")};if(e.startsWith(U))return{relativePattern:e,root:rootPathForSource(t)};let o=e;return e.startsWith(`.${U}`)&&(o=e.slice(2)),{relativePattern:o,root:null}}function getPatternsByRoot(e,t,o){const r=(()=>{switch(t){case"edit":return m;case"read":return y}})(),s=_(e,r,o),n=new Map;for(const[e,t]of s.entries()){const{relativePattern:o,root:r}=patternWithRoot(e,t.source);let s=n.get(r);void 0===s&&(s=new Map,n.set(r,s)),s.set(o,t)}return n}export function matchingRuleForInput(e,t,o,s){let n=W(e);"windows"===j()&&n.includes("\\")&&(n=N(n));const i=getPatternsByRoot(t,o,s);for(const[e,t]of i.entries()){const o=Array.from(t.keys()).map(e=>{let t=e;return t.endsWith("/**")&&(t=t.slice(0,-3)),t}),s=r().add(o),i=relativePath(e??C(),n??C());if(i.startsWith(`..${U}`))continue;if(!i)continue;const a=s.test(i);if(a.ignored&&a.rule){const e=a.rule.pattern,o=e+"/**";return t.has(o)?t.get(o)??null:t.get(e)??null}}return null}export function checkReadPermissionForTool(e,t,o){if("function"!=typeof e.getPath)return{behavior:"ask",message:`Claude requested permissions to use ${e.name}, but you haven't granted it yet.`};const r=e.getPath(t),s=k(r);for(const e of s)if(e.startsWith("\\\\")||e.startsWith("//"))return{behavior:"ask",message:`Claude requested permissions to read from ${r}, which appears to be a UNC path that could access network resources.`,decisionReason:{type:"other",reason:"UNC path detected (defense-in-depth check)"}};for(const e of s)if(hasSuspiciousWindowsPathPattern(e))return{behavior:"ask",message:`Claude requested permissions to read from ${r}, which contains a suspicious Windows path pattern that requires manual approval.`,decisionReason:{type:"other",reason:"Path contains suspicious Windows-specific patterns (alternate data streams, short names, long path prefixes, or three or more consecutive dots) that require manual verification"}};for(const e of s){const t=matchingRuleForInput(e,o,"read","deny");if(t)return{behavior:"deny",message:`Permission to read ${r} has been denied.`,decisionReason:{type:"rule",rule:t}}}for(const e of s){const t=matchingRuleForInput(e,o,"read","ask");if(t)return{behavior:"ask",message:`Context solicitó permisos para leer desde ${r}, pero aún no se los has otorgado.`,decisionReason:{type:"rule",rule:t}}}const n=checkWritePermissionForTool(e,t,o,s);if("allow"===n.behavior)return n;if(pathInAllowedWorkingPath(r,o,s))return{behavior:"allow",updatedInput:t,decisionReason:{type:"mode",mode:"default"}};const i=checkReadableInternalPath(W(r),t);if("passthrough"!==i.behavior)return i;const a=matchingRuleForInput(r,o,"read","allow");return a?{behavior:"allow",updatedInput:t,decisionReason:{type:"rule",rule:a}}:{behavior:"ask",message:`Context solicitó permisos para leer desde ${r}, pero aún no se los has otorgado.`,suggestions:generateSuggestions(r,"read",o,s),decisionReason:{type:"workingDir",reason:"Path is outside allowed working directories"}}}export function checkWritePermissionForTool(e,t,o,r){if("function"!=typeof e.getPath)return{behavior:"ask",message:`Claude requested permissions to use ${e.name}, but you haven't granted it yet.`};const s=e.getPath(t),n=r??k(s);for(const e of n){const t=matchingRuleForInput(e,o,"edit","deny");if(t)return{behavior:"deny",message:`Permission to edit ${s} has been denied.`,decisionReason:{type:"rule",rule:t}}}const i=checkEditableInternalPath(W(s),t);if("passthrough"!==i.behavior)return i;const a=matchingRuleForInput(s,{...o,alwaysAllowRules:{session:o.alwaysAllowRules.session??[]}},"edit","allow");if(a){const e=a.ruleValue.ruleContent;if(e&&(e.startsWith(f.slice(0,-2))||e.startsWith(g.slice(0,-2)))&&!e.includes("..")&&e.endsWith("/**"))return{behavior:"allow",updatedInput:t,decisionReason:{type:"rule",rule:a}}}const l=checkPathSafetyForAutoEdit(s,n);if(!l.safe){const e=getClaudeSkillScope(s),t=e?[{type:"addRules",rules:[{toolName:m,ruleContent:e.pattern}],behavior:"allow",destination:"session"}]:generateSuggestions(s,"write",o,n);return{behavior:"ask",message:l.message,suggestions:t,decisionReason:{type:"safetyCheck",reason:l.message,classifierApprovable:l.classifierApprovable}}}for(const e of n){const t=matchingRuleForInput(e,o,"edit","ask");if(t)return{behavior:"ask",message:`Context solicitó permisos para escribir en ${s}, pero aún no se los has otorgado.`,decisionReason:{type:"rule",rule:t}}}const c=pathInAllowedWorkingPath(s,o,n);if("acceptEdits"===o.mode&&c)return{behavior:"allow",updatedInput:t,decisionReason:{type:"mode",mode:o.mode}};const u=matchingRuleForInput(s,o,"edit","allow");return u?{behavior:"allow",updatedInput:t,decisionReason:{type:"rule",rule:u}}:{behavior:"ask",message:`Context solicitó permisos para escribir en ${s}, pero aún no se los has otorgado.`,suggestions:generateSuggestions(s,"write",o,n),decisionReason:c?void 0:{type:"workingDir",reason:"Path is outside allowed working directories"}}}export function generateSuggestions(e,t,o,r){const s=!pathInAllowedWorkingPath(e,o,r);if("read"===t&&s){const t=x(e);return k(t).map(e=>M(e,"session")).filter(e=>void 0!==e)}const n="default"===o.mode||"plan"===o.mode;if("write"===t||"create"===t){const t=n?[{type:"setMode",mode:"acceptEdits",destination:"session"}]:[];if(s){const o=x(e),r=k(o);t.push({type:"addDirectories",directories:r,destination:"session"})}return t}return n?[{type:"setMode",mode:"acceptEdits",destination:"session"}]:[]}export function checkEditableInternalPath(e,o){const r=l(e);if(isSessionPlanFile(r))return{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Plan files for current session are allowed for writing"}};if(isScratchpadPath(r))return{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Scratchpad files for current session are allowed for writing"}};if(t("TEMPLATES")){const t=process.env.CONTEXT_JOB_DIR??process.env.CLAUDE_JOB_DIR;if(t){const r=a(R(),"jobs"),s=k(t).map(l),n=k(r).map(l);if(s.every(e=>n.some(t=>e.startsWith(t+u)))){if(k(e).every(e=>{const t=l(e);return s.some(e=>t===e||t.startsWith(e+u))}))return{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Job directory files for current job are allowed for writing"}}}}}return h(r)?{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Agent memory files are allowed for writing"}}:!p()&&d(r)?{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"auto memory files are allowed for writing"}}:normalizeCaseForComparison(r)===normalizeCaseForComparison(a(w(),".claude","launch.json"))?{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Preview launch config is allowed for writing"}}:{behavior:"passthrough",message:""}}export function checkReadableInternalPath(e,t){const o=l(e);if(function(e){return l(e).startsWith(getSessionMemoryDir())}(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Session memory files are allowed for reading"}};if(function(e){const t=$(C()),o=l(e);return o===t||o.startsWith(t+u)}(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Project directory files are allowed for reading"}};if(isSessionPlanFile(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Plan files for current session are allowed for reading"}};const r=O(),s=r.endsWith(u)?r:r+u;if(o===r||o.startsWith(s))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Tool result files are allowed for reading"}};if(isScratchpadPath(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Scratchpad files for current session are allowed for reading"}};const n=getProjectTempDir();if(o.startsWith(n))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Project temp directory files are allowed for reading"}};if(h(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Agent memory files are allowed for reading"}};if(d(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"auto memory files are allowed for reading"}};const i=a(R(),"tasks")+u;if(o===i.slice(0,-1)||o.startsWith(i))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Task files are allowed for reading"}};const c=a(R(),"teams")+u;if(o===c.slice(0,-1)||o.startsWith(c))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Team files are allowed for reading"}};const p=getBundledSkillsRoot()+u;return o.startsWith(p)?{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Bundled skill reference files are allowed for reading"}}:{behavior:"passthrough",message:""}}
1
+ import{MACRO as e,feature as t}from"../../recovery/bunBundleShim.js";import{randomBytes as o}from"crypto";import r from"ignore";import s from"lodash-es/memoize.js";import{homedir as n,tmpdir as i}from"os";import{join as a,normalize as l,posix as c,sep as u}from"path";import{hasAutoMemPathOverride as p,isAutoMemPath as d}from"../../memdir/paths.js";import{isAgentMemoryPath as h}from"../../tools/AgentTool/agentMemory.js";import{CLAUDE_FOLDER_PERMISSION_PATTERN as f,FILE_EDIT_TOOL_NAME as m,GLOBAL_CLAUDE_FOLDER_PERMISSION_PATTERN as g}from"../../tools/FileEditTool/constants.js";import{getOriginalCwd as w,getSessionId as P}from"../../bootstrap/state.js";import{checkStatsigFeatureGate_CACHED_MAY_BE_STALE as v}from"../../services/analytics/growthbook.js";import{FILE_READ_TOOL_NAME as y}from"../../tools/FileReadTool/prompt.js";import{getCwd as C}from"../cwd.js";import{getClaudeConfigHomeDir as R}from"../envUtils.js";import{getFsImplementation as b,getPathsForPermissionCheck as k}from"../fsOperations.js";import{containsPathTraversal as S,expandPath as W,getDirectoryForPath as x,sanitizePath as I}from"../path.js";import{getPlanSlug as D,getPlansDirectory as F}from"../plans.js";import{getPlatform as j}from"../platform.js";import{getProjectDir as $}from"../sessionStorage.js";import{SETTING_SOURCES as E}from"../settings/constants.js";import{getSettingsFilePathForSource as T,getSettingsRootPathForSource as A}from"../settings/settings.js";import{containsVulnerableUncPath as z}from"../shell/readOnlyCommandValidation.js";import{getToolResultsDir as O}from"../toolResultStorage.js";import{windowsPathToPosixPath as N}from"../windowsPaths.js";import{createReadRuleSuggestion as M}from"./PermissionUpdate.js";import{getRuleByContentsForToolName as _}from"./permissions.js";export const DANGEROUS_FILES=[".gitconfig",".gitmodules",".bashrc",".bash_profile",".zshrc",".zprofile",".profile",".ripgreprc",".mcp.json",".claude.json",".context.json"];export const DANGEROUS_DIRECTORIES=[".git",".vscode",".idea",".claude"];export function normalizeCaseForComparison(e){return e.toLowerCase()}export function getClaudeSkillScope(e){const t=W(e),o=normalizeCaseForComparison(t),r=[{dir:W(a(w(),".contextcli","skills")),prefix:"/.contextcli/skills/"},{dir:W(a(R(),"skills")),prefix:"~/.contextcli/skills/"},{dir:W(a(w(),".contextcli","skills")),prefix:"/.contextcli/skills/"},{dir:W(a(n(),".contextcli","skills")),prefix:"~/.contextcli/skills/"}];for(const{dir:e,prefix:s}of r){const r=normalizeCaseForComparison(e);for(const n of[u,"/"])if(o.startsWith(r+n.toLowerCase())){const o=t.slice(e.length+n.length),r=o.indexOf("/"),i="\\"===u?o.indexOf("\\"):-1,a=-1===r?i:-1===i?r:Math.min(r,i);if(a<=0)return null;const l=o.slice(0,a);return!l||"."===l||l.includes("..")||/[*?[\]]/.test(l)?null:{skillName:l,pattern:s+l+"/**"}}}return null}const U=c.sep;export function relativePath(e,t){if("windows"===j()){const o=N(e),r=N(t);return c.relative(o,r)}return c.relative(e,t)}export function toPosixPath(e){return"windows"===j()?N(e):e}export function isClaudeSettingsPath(e){const t=normalizeCaseForComparison(W(e));return!(!t.endsWith(`${u}.contextcli${u}settings.json`)&&!t.endsWith(`${u}.contextcli${u}settings.local.json`))||E.map(e=>T(e)).filter(e=>void 0!==e).some(e=>normalizeCaseForComparison(e)===t)}function isClaudeConfigFilePath(e){if(isClaudeSettingsPath(e))return!0;const t=a(w(),".claude","commands"),o=a(w(),".claude","agents"),r=a(w(),".claude","skills"),s=a(w(),".contextcli","commands"),n=a(w(),".contextcli","agents"),i=a(w(),".contextcli","skills");return pathInWorkingPath(e,t)||pathInWorkingPath(e,o)||pathInWorkingPath(e,r)||pathInWorkingPath(e,s)||pathInWorkingPath(e,n)||pathInWorkingPath(e,i)}function isSessionPlanFile(e){const t=a(F(),D()),o=l(e);return o.startsWith(t)&&o.endsWith(".md")}export function getSessionMemoryDir(){return a($(C()),P(),"session-memory")+u}export function getSessionMemoryPath(){return a(getSessionMemoryDir(),"summary.md")}export function isScratchpadEnabled(){return v("tengu_scratch")}export function getClaudeTempDirName(){if("windows"===j())return"claude";return`claude-${process.getuid?.()??0}`}export const getClaudeTempDir=s(function(){const e=process.env.CONTEXT_CODE_TMPDIR||process.env.CLAUDE_CODE_TMPDIR||("windows"===j()?i():"/tmp"),t=b();let o=e;try{o=t.realpathSync(e)}catch{}return a(o,getClaudeTempDirName())+u});export const getBundledSkillsRoot=s(function(){const t=o(16).toString("hex");return a(getClaudeTempDir(),"bundled-skills",e.VERSION,t)});export function getProjectTempDir(){return a(getClaudeTempDir(),I(w()))+u}export function getScratchpadDir(){return a(getProjectTempDir(),P(),"scratchpad")}export async function ensureScratchpadDir(){if(!isScratchpadEnabled())throw new Error("Scratchpad directory feature is not enabled");const e=b(),t=getScratchpadDir();return await e.mkdir(t,{mode:448}),t}function isScratchpadPath(e){if(!isScratchpadEnabled())return!1;const t=getScratchpadDir(),o=l(e);return o===t||o.startsWith(t+u)}function isDangerousFilePathToAutoEdit(e){const t=W(e).split(u),o=t.at(-1);if(e.startsWith("\\\\")||e.startsWith("//"))return!0;for(let e=0;e<t.length;e++){const o=normalizeCaseForComparison(t[e]);for(const r of DANGEROUS_DIRECTORIES)if(o===normalizeCaseForComparison(r)){if(".claude"===r){const o=t[e+1];if(o&&"worktrees"===normalizeCaseForComparison(o))break}return!0}}if(o){const e=normalizeCaseForComparison(o);if(DANGEROUS_FILES.some(t=>normalizeCaseForComparison(t)===e))return!0}return!1}function hasSuspiciousWindowsPathPattern(e){if("windows"===j()||"wsl"===j()){if(-1!==e.indexOf(":",2))return!0}return!!/~\d/.test(e)||(!!(e.startsWith("\\\\?\\")||e.startsWith("\\\\.\\")||e.startsWith("//?/")||e.startsWith("//./"))||(!!/[.\s]+$/.test(e)||(!!/\.(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(e)||(!!/(^|\/|\\)\.{3,}(\/|\\|$)/.test(e)||!!z(e)))))}export function checkPathSafetyForAutoEdit(e,t){const o=t??k(e);for(const t of o)if(hasSuspiciousWindowsPathPattern(t))return{safe:!1,message:`Claude requested permissions to write to ${e}, which contains a suspicious Windows path pattern that requires manual approval.`,classifierApprovable:!1};for(const t of o)if(isClaudeConfigFilePath(t))return{safe:!1,message:`Context solicitó permisos para escribir en ${e}, pero aún no se los has otorgado.`,classifierApprovable:!0};for(const t of o)if(isDangerousFilePathToAutoEdit(t))return{safe:!1,message:`Claude requested permissions to edit ${e} which is a sensitive file.`,classifierApprovable:!0};return{safe:!0}}export function allWorkingDirectories(e){return new Set([w(),...e.additionalWorkingDirectories.keys()])}export const getResolvedWorkingDirPaths=s(k);export function pathInAllowedWorkingPath(e,t,o){const r=o??k(e),s=Array.from(allWorkingDirectories(t)).flatMap(e=>getResolvedWorkingDirPaths(e));return r.every(e=>s.some(t=>pathInWorkingPath(e,t)))}export function pathInWorkingPath(e,t){const o=W(e),r=W(t),s=o.replace(/^\/private\/var\//,"/var/").replace(/^\/private\/tmp(\/|$)/,"/tmp$1"),n=r.replace(/^\/private\/var\//,"/var/").replace(/^\/private\/tmp(\/|$)/,"/tmp$1"),i=normalizeCaseForComparison(s),a=relativePath(normalizeCaseForComparison(n),i);return""===a||!S(a)&&!c.isAbsolute(a)}function rootPathForSource(e){switch(e){case"cliArg":case"command":case"session":return W(w());case"userSettings":case"policySettings":case"projectSettings":case"localSettings":case"flagSettings":return A(e)}}function prependDirSep(e){return c.join(U,e)}function normalizePatternToPath({patternRoot:e,pattern:t,rootPath:o}){const r=c.join(e,t);if(e===o)return prependDirSep(t);if(r.startsWith(`${o}${U}`)){return prependDirSep(r.slice(o.length))}{const r=c.relative(o,e);if(!r||r.startsWith(`..${U}`)||".."===r)return null;return prependDirSep(c.join(r,t))}}export function normalizePatternsToPath(e,t){const o=new Set(e.get(null)??[]);for(const[r,s]of e.entries())if(null!==r)for(const e of s){const s=normalizePatternToPath({patternRoot:r,pattern:e,rootPath:t});s&&o.add(s)}return Array.from(o)}export function getFileReadIgnorePatterns(e){const t=getPatternsByRoot(e,"read","deny"),o=new Map;for(const[e,r]of t.entries())o.set(e,Array.from(r.keys()));return o}function patternWithRoot(e,t){if(e.startsWith(`${U}${U}`)){const t=e.slice(1);if("windows"===j()&&t.match(/^\/[a-z]\//i)){const e=t[1]?.toUpperCase()??"C",o=t.slice(2),r=`${e}:\\`;return{relativePattern:o.startsWith("/")?o.slice(1):o,root:r}}return{relativePattern:t,root:U}}if(e.startsWith(`~${U}`))return{relativePattern:e.slice(1),root:n().normalize("NFC")};if(e.startsWith(U))return{relativePattern:e,root:rootPathForSource(t)};let o=e;return e.startsWith(`.${U}`)&&(o=e.slice(2)),{relativePattern:o,root:null}}function getPatternsByRoot(e,t,o){const r=(()=>{switch(t){case"edit":return m;case"read":return y}})(),s=_(e,r,o),n=new Map;for(const[e,t]of s.entries()){const{relativePattern:o,root:r}=patternWithRoot(e,t.source);let s=n.get(r);void 0===s&&(s=new Map,n.set(r,s)),s.set(o,t)}return n}export function matchingRuleForInput(e,t,o,s){let n=W(e);"windows"===j()&&n.includes("\\")&&(n=N(n));const i=getPatternsByRoot(t,o,s);for(const[e,t]of i.entries()){const o=Array.from(t.keys()).map(e=>{let t=e;return t.endsWith("/**")&&(t=t.slice(0,-3)),t}),s=r().add(o),i=relativePath(e??C(),n??C());if(i.startsWith(`..${U}`))continue;if(!i)continue;const a=s.test(i);if(a.ignored&&a.rule){const e=a.rule.pattern,o=e+"/**";return t.has(o)?t.get(o)??null:t.get(e)??null}}return null}export function checkReadPermissionForTool(e,t,o){if("function"!=typeof e.getPath)return{behavior:"ask",message:`Claude requested permissions to use ${e.name}, but you haven't granted it yet.`};const r=e.getPath(t),s=k(r);for(const e of s)if(e.startsWith("\\\\")||e.startsWith("//"))return{behavior:"ask",message:`Claude requested permissions to read from ${r}, which appears to be a UNC path that could access network resources.`,decisionReason:{type:"other",reason:"UNC path detected (defense-in-depth check)"}};for(const e of s)if(hasSuspiciousWindowsPathPattern(e))return{behavior:"ask",message:`Claude requested permissions to read from ${r}, which contains a suspicious Windows path pattern that requires manual approval.`,decisionReason:{type:"other",reason:"Path contains suspicious Windows-specific patterns (alternate data streams, short names, long path prefixes, or three or more consecutive dots) that require manual verification"}};for(const e of s){const t=matchingRuleForInput(e,o,"read","deny");if(t)return{behavior:"deny",message:`Permission to read ${r} has been denied.`,decisionReason:{type:"rule",rule:t}}}for(const e of s){const t=matchingRuleForInput(e,o,"read","ask");if(t)return{behavior:"ask",message:`Context solicitó permisos para leer desde ${r}, pero aún no se los has otorgado.`,decisionReason:{type:"rule",rule:t}}}const n=checkWritePermissionForTool(e,t,o,s);if("allow"===n.behavior)return n;if(pathInAllowedWorkingPath(r,o,s))return{behavior:"allow",updatedInput:t,decisionReason:{type:"mode",mode:"default"}};const i=checkReadableInternalPath(W(r),t);if("passthrough"!==i.behavior)return i;const a=matchingRuleForInput(r,o,"read","allow");return a?{behavior:"allow",updatedInput:t,decisionReason:{type:"rule",rule:a}}:{behavior:"ask",message:`Context solicitó permisos para leer desde ${r}, pero aún no se los has otorgado.`,suggestions:generateSuggestions(r,"read",o,s),decisionReason:{type:"workingDir",reason:"Path is outside allowed working directories"}}}export function checkWritePermissionForTool(e,t,o,r){if("function"!=typeof e.getPath)return{behavior:"ask",message:`Claude requested permissions to use ${e.name}, but you haven't granted it yet.`};const s=e.getPath(t),n=r??k(s);for(const e of n){const t=matchingRuleForInput(e,o,"edit","deny");if(t)return{behavior:"deny",message:`Permission to edit ${s} has been denied.`,decisionReason:{type:"rule",rule:t}}}const i=checkEditableInternalPath(W(s),t);if("passthrough"!==i.behavior)return i;const a=matchingRuleForInput(s,{...o,alwaysAllowRules:{session:o.alwaysAllowRules.session??[]}},"edit","allow");if(a){const e=a.ruleValue.ruleContent;if(e&&(e.startsWith(f.slice(0,-2))||e.startsWith(g.slice(0,-2)))&&!e.includes("..")&&e.endsWith("/**"))return{behavior:"allow",updatedInput:t,decisionReason:{type:"rule",rule:a}}}const l=checkPathSafetyForAutoEdit(s,n);if(!l.safe){const e=getClaudeSkillScope(s),t=e?[{type:"addRules",rules:[{toolName:m,ruleContent:e.pattern}],behavior:"allow",destination:"session"}]:generateSuggestions(s,"write",o,n);return{behavior:"ask",message:l.message,suggestions:t,decisionReason:{type:"safetyCheck",reason:l.message,classifierApprovable:l.classifierApprovable}}}for(const e of n){const t=matchingRuleForInput(e,o,"edit","ask");if(t)return{behavior:"ask",message:`Context solicitó permisos para escribir en ${s}, pero aún no se los has otorgado.`,decisionReason:{type:"rule",rule:t}}}const c=pathInAllowedWorkingPath(s,o,n);if("acceptEdits"===o.mode&&c)return{behavior:"allow",updatedInput:t,decisionReason:{type:"mode",mode:o.mode}};const u=matchingRuleForInput(s,o,"edit","allow");return u?{behavior:"allow",updatedInput:t,decisionReason:{type:"rule",rule:u}}:{behavior:"ask",message:`Context solicitó permisos para escribir en ${s}, pero aún no se los has otorgado.`,suggestions:generateSuggestions(s,"write",o,n),decisionReason:c?void 0:{type:"workingDir",reason:"Path is outside allowed working directories"}}}export function generateSuggestions(e,t,o,r){const s=!pathInAllowedWorkingPath(e,o,r);if("read"===t&&s){const t=x(e);return k(t).map(e=>M(e,"session")).filter(e=>void 0!==e)}const n="default"===o.mode||"plan"===o.mode;if("write"===t||"create"===t){const t=n?[{type:"setMode",mode:"acceptEdits",destination:"session"}]:[];if(s){const o=x(e),r=k(o);t.push({type:"addDirectories",directories:r,destination:"session"})}return t}return n?[{type:"setMode",mode:"acceptEdits",destination:"session"}]:[]}export function checkEditableInternalPath(e,o){const r=l(e);if(isSessionPlanFile(r))return{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Plan files for current session are allowed for writing"}};if(isScratchpadPath(r))return{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Scratchpad files for current session are allowed for writing"}};if(t("TEMPLATES")){const t=process.env.CONTEXT_JOB_DIR??process.env.CLAUDE_JOB_DIR;if(t){const r=a(R(),"jobs"),s=k(t).map(l),n=k(r).map(l);if(s.every(e=>n.some(t=>e.startsWith(t+u)))){if(k(e).every(e=>{const t=l(e);return s.some(e=>t===e||t.startsWith(e+u))}))return{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Job directory files for current job are allowed for writing"}}}}}return h(r)?{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Agent memory files are allowed for writing"}}:!p()&&d(r)?{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"auto memory files are allowed for writing"}}:normalizeCaseForComparison(r)===normalizeCaseForComparison(a(w(),".claude","launch.json"))?{behavior:"allow",updatedInput:o,decisionReason:{type:"other",reason:"Preview launch config is allowed for writing"}}:{behavior:"passthrough",message:""}}export function checkReadableInternalPath(e,t){const o=l(e);if(function(e){return l(e).startsWith(getSessionMemoryDir())}(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Session memory files are allowed for reading"}};if(function(e){const t=$(C()),o=l(e);return o===t||o.startsWith(t+u)}(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Project directory files are allowed for reading"}};if(isSessionPlanFile(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Plan files for current session are allowed for reading"}};const r=O(),s=r.endsWith(u)?r:r+u;if(o===r||o.startsWith(s))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Tool result files are allowed for reading"}};if(isScratchpadPath(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Scratchpad files for current session are allowed for reading"}};const n=getProjectTempDir();if(o.startsWith(n))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Project temp directory files are allowed for reading"}};if(h(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Agent memory files are allowed for reading"}};if(d(o))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"auto memory files are allowed for reading"}};const i=a(R(),"tasks")+u;if(o===i.slice(0,-1)||o.startsWith(i))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Task files are allowed for reading"}};const c=a(R(),"teams")+u;if(o===c.slice(0,-1)||o.startsWith(c))return{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Team files are allowed for reading"}};const p=getBundledSkillsRoot()+u;return o.startsWith(p)?{behavior:"allow",updatedInput:t,decisionReason:{type:"other",reason:"Bundled skill reference files are allowed for reading"}}:{behavior:"passthrough",message:""}}
@@ -1 +1 @@
1
- import{join as t}from"path";import{getAdditionalDirectoriesForClaudeMd as n}from"../../bootstrap/state.js";import{parseSettingsFile as o}from"../settings/settings.js";const s=["settings.json","settings.local.json"],e=[".context",".claude"];function readAddDirSettings(n,s){for(const r of e){const{settings:e}=o(t(n,r,s));if(e)return e}return null}export function getAddDirEnabledPlugins(){const t={};for(const o of n())for(const n of s){const s=readAddDirSettings(o,n);s?.enabledPlugins&&Object.assign(t,s.enabledPlugins)}return t}export function getAddDirExtraMarketplaces(){const t={};for(const o of n())for(const n of s){const s=readAddDirSettings(o,n);s?.extraKnownMarketplaces&&Object.assign(t,s.extraKnownMarketplaces)}return t}
1
+ import{join as t}from"path";import{getAdditionalDirectoriesForClaudeMd as n}from"../../bootstrap/state.js";import{parseSettingsFile as o}from"../settings/settings.js";const s=["settings.json","settings.local.json"],e=[".contextcli",".claude"];function readAddDirSettings(n,s){for(const r of e){const{settings:e}=o(t(n,r,s));if(e)return e}return null}export function getAddDirEnabledPlugins(){const t={};for(const o of n())for(const n of s){const s=readAddDirSettings(o,n);s?.enabledPlugins&&Object.assign(t,s.enabledPlugins)}return t}export function getAddDirExtraMarketplaces(){const t={};for(const o of n())for(const n of s){const s=readAddDirSettings(o,n);s?.extraKnownMarketplaces&&Object.assign(t,s.extraKnownMarketplaces)}return t}
@@ -1 +1 @@
1
- import{SandboxManager as o,SandboxRuntimeConfigSchema as n,SandboxViolationStore as t}from"@anthropic-ai/sandbox-runtime";import{rmSync as e,statSync as s}from"fs";import{readFile as a}from"fs/promises";import{memoize as r}from"lodash-es";import{join as i,resolve as l,sep as d}from"path";import{getAdditionalDirectoriesForClaudeMd as c,getCwdState as u,getOriginalCwd as b}from"../../bootstrap/state.js";import{logForDebugging as f}from"../debug.js";import{expandPath as m}from"../path.js";import{getPlatform as g}from"../platform.js";import{settingsChangeDetector as x}from"../settings/changeDetector.js";import{SETTING_SOURCES as p}from"../settings/constants.js";import{getManagedSettingsDropInDir as S}from"../settings/managedPath.js";import{getInitialSettings as h,getSettings_DEPRECATED as w,getSettingsFilePathForSource as y,getSettingsForSource as P,getSettingsRootPathForSource as k,updateSettingsForSource as C}from"../settings/settings.js";import{BASH_TOOL_NAME as v}from"../../tools/BashTool/toolName.js";import{FILE_EDIT_TOOL_NAME as F}from"../../tools/FileEditTool/constants.js";import{FILE_READ_TOOL_NAME as W}from"../../tools/FileReadTool/prompt.js";import{WEB_FETCH_TOOL_NAME as R}from"../../tools/WebFetchTool/prompt.js";import{errorMessage as E}from"../errors.js";import{getClaudeTempDir as A}from"../permissions/filesystem.js";import{ripgrepCommand as N}from"../ripgrep.js";function permissionRuleValueFromString(o){const n=o.match(/^([^(]+)\(([^)]+)\)$/);if(!n)return{toolName:o};const t=n[1],e=n[2];return t&&e?{toolName:t,ruleContent:e}:{toolName:o}}export function resolvePathPatternForSandbox(o,n){if(o.startsWith("//"))return o.slice(1);if(o.startsWith("/")&&!o.startsWith("//")){const t=k(n);return l(t,o.slice(1))}return o}export function resolveSandboxFilesystemPath(o,n){return o.startsWith("//")?o.slice(1):m(o,k(n))}export function shouldAllowManagedSandboxDomainsOnly(){return!0===P("policySettings")?.sandbox?.network?.allowManagedDomainsOnly}function shouldAllowManagedReadPathsOnly(){return!0===P("policySettings")?.sandbox?.filesystem?.allowManagedReadPathsOnly}export function convertToSandboxRuntimeConfig(o){const n=o.permissions||{},t=[],e=[];if(shouldAllowManagedSandboxDomainsOnly()){const o=P("policySettings");for(const n of o?.sandbox?.network?.allowedDomains||[])t.push(n);for(const n of o?.permissions?.allow||[]){const o=permissionRuleValueFromString(n);o.toolName===R&&o.ruleContent?.startsWith("domain:")&&t.push(o.ruleContent.substring(7))}}else{for(const n of o.sandbox?.network?.allowedDomains||[])t.push(n);for(const o of n.allow||[]){const n=permissionRuleValueFromString(o);n.toolName===R&&n.ruleContent?.startsWith("domain:")&&t.push(n.ruleContent.substring(7))}}for(const o of n.deny||[]){const n=permissionRuleValueFromString(o);n.toolName===R&&n.ruleContent?.startsWith("domain:")&&e.push(n.ruleContent.substring(7))}const a=[".",A()],r=[],i=[],d=[],f=p.map(o=>y(o)).filter(o=>void 0!==o);r.push(...f),r.push(S());const m=u(),g=b();m!==g&&(r.push(l(m,".claude","settings.json")),r.push(l(m,".claude","settings.local.json"))),r.push(l(g,".claude","skills")),r.push(l(g,".context","skills")),m!==g&&(r.push(l(m,".claude","skills")),r.push(l(m,".context","skills"))),$.length=0;const x=["HEAD","objects","refs","hooks","config"];for(const o of m===g?[g]:[g,m])for(const n of x){const t=l(o,n);try{s(t),r.push(t)}catch{$.push(t)}}j&&j!==m&&a.push(j);const h=new Set([...o.permissions?.additionalDirectories||[],...c()]);a.push(...h);for(const o of p){const n=P(o);if(n?.permissions){for(const t of n.permissions.allow||[]){const n=permissionRuleValueFromString(t);n.toolName===F&&n.ruleContent&&a.push(resolvePathPatternForSandbox(n.ruleContent,o))}for(const t of n.permissions.deny||[]){const n=permissionRuleValueFromString(t);n.toolName===F&&n.ruleContent&&r.push(resolvePathPatternForSandbox(n.ruleContent,o)),n.toolName===W&&n.ruleContent&&i.push(resolvePathPatternForSandbox(n.ruleContent,o))}}const t=n?.sandbox?.filesystem;if(t){for(const n of t.allowWrite||[])a.push(resolveSandboxFilesystemPath(n,o));for(const n of t.denyWrite||[])r.push(resolveSandboxFilesystemPath(n,o));for(const n of t.denyRead||[])i.push(resolveSandboxFilesystemPath(n,o));if(!shouldAllowManagedReadPathsOnly()||"policySettings"===o)for(const n of t.allowRead||[])d.push(resolveSandboxFilesystemPath(n,o))}}const{rgPath:w,rgArgs:k,argv0:C}=N(),v=o.sandbox?.ripgrep??{command:w,args:k,argv0:C};return{network:{allowedDomains:t,deniedDomains:e,allowUnixSockets:o.sandbox?.network?.allowUnixSockets,allowAllUnixSockets:o.sandbox?.network?.allowAllUnixSockets,allowLocalBinding:o.sandbox?.network?.allowLocalBinding,httpProxyPort:o.sandbox?.network?.httpProxyPort,socksProxyPort:o.sandbox?.network?.socksProxyPort},filesystem:{denyRead:i,allowRead:d,allowWrite:a,denyWrite:r},ignoreViolations:o.sandbox?.ignoreViolations,enableWeakerNestedSandbox:o.sandbox?.enableWeakerNestedSandbox,enableWeakerNetworkIsolation:o.sandbox?.enableWeakerNetworkIsolation,ripgrep:v}}let I,L,j;const $=[];const D=r(()=>{const{rgPath:n,rgArgs:t}=N();return o.checkDependencies({command:n,args:t})});function getSandboxEnabledSetting(){try{const o=w();return o?.sandbox?.enabled??!1}catch(o){return f(`Failed to get settings for sandbox check: ${o}`),!1}}const U=r(()=>o.isSupportedPlatform());function isPlatformInEnabledList(){try{const o=h(),n=o?.sandbox?.enabledPlatforms;if(void 0===n)return!0;if(0===n.length)return!1;const t=g();return n.includes(t)}catch(o){return f(`Failed to check enabledPlatforms: ${o}`),!0}}function isSandboxingEnabled(){return!!U()&&(!(D().errors.length>0)&&(!!isPlatformInEnabledList()&&getSandboxEnabledSetting()))}export function addToExcludedCommands(o,n){const t=P("localSettings"),e=t?.sandbox?.excludedCommands||[];let s=o;if(n){const o=n.filter(o=>"addRules"===o.type&&o.rules.some(o=>o.toolName===v));if(o.length>0&&"addRules"===o[0].type){const n=o[0].rules.find(o=>o.toolName===v);if(n?.ruleContent){s=function(o){const n=o.match(/^(.+):\*$/);return n?.[1]??null}(n.ruleContent)||n.ruleContent}}}return e.includes(s)||C("localSettings",{sandbox:{...t?.sandbox,excludedCommands:[...e,s]}}),s}export const SandboxManager={initialize:async function(n){if(I)return I;if(!isSandboxingEnabled())return;const t=n?async o=>shouldAllowManagedSandboxDomainsOnly()?(f(`[sandbox] Blocked network request to ${o.host} (allowManagedDomainsOnly)`),!1):n(o):void 0;return I=(async()=>{try{void 0===j&&(j=await async function(o){const n=i(o,".git");try{const t=(await a(n,{encoding:"utf8"})).match(/^gitdir:\s*(.+)$/m);if(!t?.[1])return null;const e=l(o,t[1].trim()),s=`${d}.git${d}worktrees${d}`,r=e.lastIndexOf(s);return r>0?e.substring(0,r):null}catch{return null}}(u()));const n=convertToSandboxRuntimeConfig(w());await o.initialize(n,t),L=x.subscribe(()=>{const n=convertToSandboxRuntimeConfig(w());o.updateConfig(n),f("Sandbox configuration updated from settings change")})}catch(o){I=void 0,f(`Failed to initialize sandbox: ${E(o)}`)}})(),I},isSandboxingEnabled,isSandboxEnabledInSettings:getSandboxEnabledSetting,isPlatformInEnabledList,getSandboxUnavailableReason:function(){if(!getSandboxEnabledSetting())return;if(!U()){const o=g();return"wsl"===o?"sandbox.enabled is set but WSL1 is not supported (requires WSL2)":`sandbox.enabled is set but ${o} is not supported (requires macOS, Linux, or WSL2)`}if(!isPlatformInEnabledList())return`sandbox.enabled is set but ${g()} is not in sandbox.enabledPlatforms`;const o=D();if(o.errors.length>0){const n="macos"===g()?"run /sandbox or /doctor for details":"install missing tools (e.g. apt install bubblewrap socat) or run /sandbox for details";return`sandbox.enabled is set but dependencies are missing: ${o.errors.join(", ")} · ${n}`}},isAutoAllowBashIfSandboxedEnabled:function(){const o=w();return o?.sandbox?.autoAllowBashIfSandboxed??!0},areUnsandboxedCommandsAllowed:function(){const o=w();return o?.sandbox?.allowUnsandboxedCommands??!0},isSandboxRequired:function(){const o=w();return getSandboxEnabledSetting()&&(o?.sandbox?.failIfUnavailable??!1)},areSandboxSettingsLockedByPolicy:function(){const o=["flagSettings","policySettings"];for(const n of o){const o=P(n);if(void 0!==o?.sandbox?.enabled||void 0!==o?.sandbox?.autoAllowBashIfSandboxed||void 0!==o?.sandbox?.allowUnsandboxedCommands)return!0}return!1},setSandboxSettings:async function(o){const n=P("localSettings");C("localSettings",{sandbox:{...n?.sandbox,...void 0!==o.enabled&&{enabled:o.enabled},...void 0!==o.autoAllowBashIfSandboxed&&{autoAllowBashIfSandboxed:o.autoAllowBashIfSandboxed},...void 0!==o.allowUnsandboxedCommands&&{allowUnsandboxedCommands:o.allowUnsandboxedCommands}}})},getExcludedCommands:function(){const o=w();return o?.sandbox?.excludedCommands??[]},wrapWithSandbox:async function(n,t,e,s){if(isSandboxingEnabled()){if(!I)throw new Error("Sandbox failed to initialize. ");await I}return o.wrapWithSandbox(n,t,e,s)},refreshConfig:function(){if(!isSandboxingEnabled())return;const n=convertToSandboxRuntimeConfig(w());o.updateConfig(n)},reset:async function(){return L?.(),L=void 0,j=void 0,$.length=0,D.cache.clear?.(),U.cache.clear?.(),I=void 0,o.reset()},checkDependencies:D,getFsReadConfig:o.getFsReadConfig,getFsWriteConfig:o.getFsWriteConfig,getNetworkRestrictionConfig:o.getNetworkRestrictionConfig,getIgnoreViolations:o.getIgnoreViolations,getLinuxGlobPatternWarnings:function(){const o=g();if("linux"!==o&&"wsl"!==o)return[];try{const o=w();if(!o?.sandbox?.enabled)return[];const n=o?.permissions||{},t=[],hasGlobs=o=>{const n=o.replace(/\/\*\*$/,"");return/[*?[\]]/.test(n)};for(const o of[...n.allow||[],...n.deny||[]]){const n=permissionRuleValueFromString(o);(n.toolName===F||n.toolName===W)&&n.ruleContent&&hasGlobs(n.ruleContent)&&t.push(o)}return t}catch(o){return f(`Failed to get Linux glob pattern warnings: ${o}`),[]}},isSupportedPlatform:U,getAllowUnixSockets:o.getAllowUnixSockets,getAllowLocalBinding:o.getAllowLocalBinding,getEnableWeakerNestedSandbox:o.getEnableWeakerNestedSandbox,getProxyPort:o.getProxyPort,getSocksProxyPort:o.getSocksProxyPort,getLinuxHttpSocketPath:o.getLinuxHttpSocketPath,getLinuxSocksSocketPath:o.getLinuxSocksSocketPath,waitForNetworkInitialization:o.waitForNetworkInitialization,getSandboxViolationStore:o.getSandboxViolationStore,annotateStderrWithSandboxFailures:o.annotateStderrWithSandboxFailures,cleanupAfterCommand:()=>{o.cleanupAfterCommand(),function(){for(const o of $)try{e(o,{recursive:!0}),f(`[Sandbox] scrubbed planted bare-repo file: ${o}`)}catch{}}()}};export{t as SandboxViolationStore,n as SandboxRuntimeConfigSchema};
1
+ import{SandboxManager as o,SandboxRuntimeConfigSchema as n,SandboxViolationStore as t}from"@anthropic-ai/sandbox-runtime";import{rmSync as e,statSync as s}from"fs";import{readFile as a}from"fs/promises";import{memoize as r}from"lodash-es";import{join as i,resolve as l,sep as d}from"path";import{getAdditionalDirectoriesForClaudeMd as c,getCwdState as u,getOriginalCwd as b}from"../../bootstrap/state.js";import{logForDebugging as f}from"../debug.js";import{expandPath as m}from"../path.js";import{getPlatform as g}from"../platform.js";import{settingsChangeDetector as x}from"../settings/changeDetector.js";import{SETTING_SOURCES as p}from"../settings/constants.js";import{getManagedSettingsDropInDir as S}from"../settings/managedPath.js";import{getInitialSettings as h,getSettings_DEPRECATED as w,getSettingsFilePathForSource as y,getSettingsForSource as P,getSettingsRootPathForSource as k,updateSettingsForSource as C}from"../settings/settings.js";import{BASH_TOOL_NAME as v}from"../../tools/BashTool/toolName.js";import{FILE_EDIT_TOOL_NAME as F}from"../../tools/FileEditTool/constants.js";import{FILE_READ_TOOL_NAME as W}from"../../tools/FileReadTool/prompt.js";import{WEB_FETCH_TOOL_NAME as R}from"../../tools/WebFetchTool/prompt.js";import{errorMessage as E}from"../errors.js";import{getClaudeTempDir as A}from"../permissions/filesystem.js";import{ripgrepCommand as N}from"../ripgrep.js";function permissionRuleValueFromString(o){const n=o.match(/^([^(]+)\(([^)]+)\)$/);if(!n)return{toolName:o};const t=n[1],e=n[2];return t&&e?{toolName:t,ruleContent:e}:{toolName:o}}export function resolvePathPatternForSandbox(o,n){if(o.startsWith("//"))return o.slice(1);if(o.startsWith("/")&&!o.startsWith("//")){const t=k(n);return l(t,o.slice(1))}return o}export function resolveSandboxFilesystemPath(o,n){return o.startsWith("//")?o.slice(1):m(o,k(n))}export function shouldAllowManagedSandboxDomainsOnly(){return!0===P("policySettings")?.sandbox?.network?.allowManagedDomainsOnly}function shouldAllowManagedReadPathsOnly(){return!0===P("policySettings")?.sandbox?.filesystem?.allowManagedReadPathsOnly}export function convertToSandboxRuntimeConfig(o){const n=o.permissions||{},t=[],e=[];if(shouldAllowManagedSandboxDomainsOnly()){const o=P("policySettings");for(const n of o?.sandbox?.network?.allowedDomains||[])t.push(n);for(const n of o?.permissions?.allow||[]){const o=permissionRuleValueFromString(n);o.toolName===R&&o.ruleContent?.startsWith("domain:")&&t.push(o.ruleContent.substring(7))}}else{for(const n of o.sandbox?.network?.allowedDomains||[])t.push(n);for(const o of n.allow||[]){const n=permissionRuleValueFromString(o);n.toolName===R&&n.ruleContent?.startsWith("domain:")&&t.push(n.ruleContent.substring(7))}}for(const o of n.deny||[]){const n=permissionRuleValueFromString(o);n.toolName===R&&n.ruleContent?.startsWith("domain:")&&e.push(n.ruleContent.substring(7))}const a=[".",A()],r=[],i=[],d=[],f=p.map(o=>y(o)).filter(o=>void 0!==o);r.push(...f),r.push(S());const m=u(),g=b();m!==g&&(r.push(l(m,".claude","settings.json")),r.push(l(m,".claude","settings.local.json"))),r.push(l(g,".claude","skills")),r.push(l(g,".contextcli","skills")),m!==g&&(r.push(l(m,".claude","skills")),r.push(l(m,".contextcli","skills"))),$.length=0;const x=["HEAD","objects","refs","hooks","config"];for(const o of m===g?[g]:[g,m])for(const n of x){const t=l(o,n);try{s(t),r.push(t)}catch{$.push(t)}}j&&j!==m&&a.push(j);const h=new Set([...o.permissions?.additionalDirectories||[],...c()]);a.push(...h);for(const o of p){const n=P(o);if(n?.permissions){for(const t of n.permissions.allow||[]){const n=permissionRuleValueFromString(t);n.toolName===F&&n.ruleContent&&a.push(resolvePathPatternForSandbox(n.ruleContent,o))}for(const t of n.permissions.deny||[]){const n=permissionRuleValueFromString(t);n.toolName===F&&n.ruleContent&&r.push(resolvePathPatternForSandbox(n.ruleContent,o)),n.toolName===W&&n.ruleContent&&i.push(resolvePathPatternForSandbox(n.ruleContent,o))}}const t=n?.sandbox?.filesystem;if(t){for(const n of t.allowWrite||[])a.push(resolveSandboxFilesystemPath(n,o));for(const n of t.denyWrite||[])r.push(resolveSandboxFilesystemPath(n,o));for(const n of t.denyRead||[])i.push(resolveSandboxFilesystemPath(n,o));if(!shouldAllowManagedReadPathsOnly()||"policySettings"===o)for(const n of t.allowRead||[])d.push(resolveSandboxFilesystemPath(n,o))}}const{rgPath:w,rgArgs:k,argv0:C}=N(),v=o.sandbox?.ripgrep??{command:w,args:k,argv0:C};return{network:{allowedDomains:t,deniedDomains:e,allowUnixSockets:o.sandbox?.network?.allowUnixSockets,allowAllUnixSockets:o.sandbox?.network?.allowAllUnixSockets,allowLocalBinding:o.sandbox?.network?.allowLocalBinding,httpProxyPort:o.sandbox?.network?.httpProxyPort,socksProxyPort:o.sandbox?.network?.socksProxyPort},filesystem:{denyRead:i,allowRead:d,allowWrite:a,denyWrite:r},ignoreViolations:o.sandbox?.ignoreViolations,enableWeakerNestedSandbox:o.sandbox?.enableWeakerNestedSandbox,enableWeakerNetworkIsolation:o.sandbox?.enableWeakerNetworkIsolation,ripgrep:v}}let I,L,j;const $=[];const D=r(()=>{const{rgPath:n,rgArgs:t}=N();return o.checkDependencies({command:n,args:t})});function getSandboxEnabledSetting(){try{const o=w();return o?.sandbox?.enabled??!1}catch(o){return f(`Failed to get settings for sandbox check: ${o}`),!1}}const U=r(()=>o.isSupportedPlatform());function isPlatformInEnabledList(){try{const o=h(),n=o?.sandbox?.enabledPlatforms;if(void 0===n)return!0;if(0===n.length)return!1;const t=g();return n.includes(t)}catch(o){return f(`Failed to check enabledPlatforms: ${o}`),!0}}function isSandboxingEnabled(){return!!U()&&(!(D().errors.length>0)&&(!!isPlatformInEnabledList()&&getSandboxEnabledSetting()))}export function addToExcludedCommands(o,n){const t=P("localSettings"),e=t?.sandbox?.excludedCommands||[];let s=o;if(n){const o=n.filter(o=>"addRules"===o.type&&o.rules.some(o=>o.toolName===v));if(o.length>0&&"addRules"===o[0].type){const n=o[0].rules.find(o=>o.toolName===v);if(n?.ruleContent){s=function(o){const n=o.match(/^(.+):\*$/);return n?.[1]??null}(n.ruleContent)||n.ruleContent}}}return e.includes(s)||C("localSettings",{sandbox:{...t?.sandbox,excludedCommands:[...e,s]}}),s}export const SandboxManager={initialize:async function(n){if(I)return I;if(!isSandboxingEnabled())return;const t=n?async o=>shouldAllowManagedSandboxDomainsOnly()?(f(`[sandbox] Blocked network request to ${o.host} (allowManagedDomainsOnly)`),!1):n(o):void 0;return I=(async()=>{try{void 0===j&&(j=await async function(o){const n=i(o,".git");try{const t=(await a(n,{encoding:"utf8"})).match(/^gitdir:\s*(.+)$/m);if(!t?.[1])return null;const e=l(o,t[1].trim()),s=`${d}.git${d}worktrees${d}`,r=e.lastIndexOf(s);return r>0?e.substring(0,r):null}catch{return null}}(u()));const n=convertToSandboxRuntimeConfig(w());await o.initialize(n,t),L=x.subscribe(()=>{const n=convertToSandboxRuntimeConfig(w());o.updateConfig(n),f("Sandbox configuration updated from settings change")})}catch(o){I=void 0,f(`Failed to initialize sandbox: ${E(o)}`)}})(),I},isSandboxingEnabled,isSandboxEnabledInSettings:getSandboxEnabledSetting,isPlatformInEnabledList,getSandboxUnavailableReason:function(){if(!getSandboxEnabledSetting())return;if(!U()){const o=g();return"wsl"===o?"sandbox.enabled is set but WSL1 is not supported (requires WSL2)":`sandbox.enabled is set but ${o} is not supported (requires macOS, Linux, or WSL2)`}if(!isPlatformInEnabledList())return`sandbox.enabled is set but ${g()} is not in sandbox.enabledPlatforms`;const o=D();if(o.errors.length>0){const n="macos"===g()?"run /sandbox or /doctor for details":"install missing tools (e.g. apt install bubblewrap socat) or run /sandbox for details";return`sandbox.enabled is set but dependencies are missing: ${o.errors.join(", ")} · ${n}`}},isAutoAllowBashIfSandboxedEnabled:function(){const o=w();return o?.sandbox?.autoAllowBashIfSandboxed??!0},areUnsandboxedCommandsAllowed:function(){const o=w();return o?.sandbox?.allowUnsandboxedCommands??!0},isSandboxRequired:function(){const o=w();return getSandboxEnabledSetting()&&(o?.sandbox?.failIfUnavailable??!1)},areSandboxSettingsLockedByPolicy:function(){const o=["flagSettings","policySettings"];for(const n of o){const o=P(n);if(void 0!==o?.sandbox?.enabled||void 0!==o?.sandbox?.autoAllowBashIfSandboxed||void 0!==o?.sandbox?.allowUnsandboxedCommands)return!0}return!1},setSandboxSettings:async function(o){const n=P("localSettings");C("localSettings",{sandbox:{...n?.sandbox,...void 0!==o.enabled&&{enabled:o.enabled},...void 0!==o.autoAllowBashIfSandboxed&&{autoAllowBashIfSandboxed:o.autoAllowBashIfSandboxed},...void 0!==o.allowUnsandboxedCommands&&{allowUnsandboxedCommands:o.allowUnsandboxedCommands}}})},getExcludedCommands:function(){const o=w();return o?.sandbox?.excludedCommands??[]},wrapWithSandbox:async function(n,t,e,s){if(isSandboxingEnabled()){if(!I)throw new Error("Sandbox failed to initialize. ");await I}return o.wrapWithSandbox(n,t,e,s)},refreshConfig:function(){if(!isSandboxingEnabled())return;const n=convertToSandboxRuntimeConfig(w());o.updateConfig(n)},reset:async function(){return L?.(),L=void 0,j=void 0,$.length=0,D.cache.clear?.(),U.cache.clear?.(),I=void 0,o.reset()},checkDependencies:D,getFsReadConfig:o.getFsReadConfig,getFsWriteConfig:o.getFsWriteConfig,getNetworkRestrictionConfig:o.getNetworkRestrictionConfig,getIgnoreViolations:o.getIgnoreViolations,getLinuxGlobPatternWarnings:function(){const o=g();if("linux"!==o&&"wsl"!==o)return[];try{const o=w();if(!o?.sandbox?.enabled)return[];const n=o?.permissions||{},t=[],hasGlobs=o=>{const n=o.replace(/\/\*\*$/,"");return/[*?[\]]/.test(n)};for(const o of[...n.allow||[],...n.deny||[]]){const n=permissionRuleValueFromString(o);(n.toolName===F||n.toolName===W)&&n.ruleContent&&hasGlobs(n.ruleContent)&&t.push(o)}return t}catch(o){return f(`Failed to get Linux glob pattern warnings: ${o}`),[]}},isSupportedPlatform:U,getAllowUnixSockets:o.getAllowUnixSockets,getAllowLocalBinding:o.getAllowLocalBinding,getEnableWeakerNestedSandbox:o.getEnableWeakerNestedSandbox,getProxyPort:o.getProxyPort,getSocksProxyPort:o.getSocksProxyPort,getLinuxHttpSocketPath:o.getLinuxHttpSocketPath,getLinuxSocksSocketPath:o.getLinuxSocksSocketPath,waitForNetworkInitialization:o.waitForNetworkInitialization,getSandboxViolationStore:o.getSandboxViolationStore,annotateStderrWithSandboxFailures:o.annotateStderrWithSandboxFailures,cleanupAfterCommand:()=>{o.cleanupAfterCommand(),function(){for(const o of $)try{e(o,{recursive:!0}),f(`[Sandbox] scrubbed planted bare-repo file: ${o}`)}catch{}}()}};export{t as SandboxViolationStore,n as SandboxRuntimeConfigSchema};
@@ -1 +1 @@
1
- import{feature as t}from"../../recovery/bunBundleShim.js";import e from"lodash-es/mergeWith.js";import{dirname as s,join as n,resolve as o}from"path";import{z as r}from"zod/v4";import{getFlagSettingsInline as i,getFlagSettingsPath as a,getOriginalCwd as g,getUseCoworkPlugins as c}from"../../bootstrap/state.js";import{getRemoteManagedSettingsSyncFromCache as l}from"../../services/remoteManagedSettings/syncCacheState.js";import{uniq as u}from"../array.js";import{logForDebugging as S}from"../debug.js";import{logForDiagnosticsNoPII as f}from"../diagLogs.js";import{getClaudeConfigHomeDir as p,isEnvTruthy as m}from"../envUtils.js";import{getErrnoCode as d,isENOENT as h}from"../errors.js";import{writeFileSyncAndFlush_DEPRECATED as y}from"../file.js";import{readFileSync as F}from"../fileRead.js";import{getFsImplementation as P,safeResolvePath as j}from"../fsOperations.js";import{addFileGlobRuleToGitignore as k}from"../git/gitignore.js";import{safeParseJSON as b}from"../json.js";import{logError as M}from"../log.js";import{getPlatform as x}from"../platform.js";import{clone as C,jsonStringify as v}from"../slowOperations.js";import{profileCheckpoint as A}from"../startupProfiler.js";import{getEnabledSettingSources as O}from"./constants.js";import{markInternalWrite as R}from"./internalWrites.js";import{getManagedFilePath as w,getManagedSettingsDropInDir as E}from"./managedPath.js";import{getHkcuSettings as _,getMdmSettings as I}from"./mdm/settings.js";import{getCachedParsedFile as D,getCachedSettingsForSource as T,getPluginSettingsBase as $,getSessionSettingsCache as U,resetSettingsCache as N,setCachedParsedFile as W,setCachedSettingsForSource as L,setSessionSettingsCache as z}from"./settingsCache.js";import{SettingsSchema as B}from"./types.js";import{filterInvalidPermissionRules as K,formatZodError as G}from"./validation.js";function getManagedSettingsFilePath(){return n(w(),"managed-settings.json")}export function loadManagedFileSettings(){const t=[];let s={},o=!1;const{settings:r,errors:i}=parseSettingsFile(getManagedSettingsFilePath());t.push(...i),r&&Object.keys(r).length>0&&(s=e(s,r,settingsMergeCustomizer),o=!0);const a=E();try{const r=P().readdirSync(a).filter(t=>(t.isFile()||t.isSymbolicLink())&&t.name.endsWith(".json")&&!t.name.startsWith(".")).map(t=>t.name).sort();for(const i of r){const{settings:r,errors:g}=parseSettingsFile(n(a,i));t.push(...g),r&&Object.keys(r).length>0&&(s=e(s,r,settingsMergeCustomizer),o=!0)}}catch(t){const e=d(t);"ENOENT"!==e&&"ENOTDIR"!==e&&M(t)}return{settings:o?s:null,errors:t}}export function getManagedFileSettingsPresence(){const{settings:t}=parseSettingsFile(getManagedSettingsFilePath()),e=!!t&&Object.keys(t).length>0;let s=!1;const n=E();try{s=P().readdirSync(n).some(t=>(t.isFile()||t.isSymbolicLink())&&t.name.endsWith(".json")&&!t.name.startsWith("."))}catch{}return{hasBase:e,hasDropIns:s}}function handleFileSystemError(t,e){"object"==typeof t&&t&&"code"in t&&"ENOENT"===t.code?S(`Broken symlink or missing file encountered for settings.json at path: ${e}`):M(t)}export function parseSettingsFile(t){const e=D(t);if(e)return{settings:e.settings?C(e.settings):null,errors:e.errors};const s=function(t){try{const{resolvedPath:e}=j(P(),t),s=F(e);if(""===s.trim())return{settings:{},errors:[]};const n=b(s,!1),o=K(n,t),r=B().safeParse(n);if(!r.success){const e=G(r.error,t);return{settings:null,errors:[...o,...e]}}return{settings:r.data,errors:o}}catch(e){return handleFileSystemError(e,t),{settings:null,errors:[]}}}(t);return W(t,s),{settings:s.settings?C(s.settings):null,errors:s.errors}}export function getSettingsRootPathForSource(t){switch(t){case"userSettings":return o(p());case"policySettings":case"projectSettings":case"localSettings":return o(g());case"flagSettings":{const t=a();return t?s(o(t)):o(g())}}}export function getSettingsFilePathForSource(t){switch(t){case"userSettings":return n(getSettingsRootPathForSource(t),c()||m(process.env.CONTEXT_CODE_USE_COWORK_PLUGINS)||m(process.env.CLAUDE_CODE_USE_COWORK_PLUGINS)?"cowork_settings.json":"settings.json");case"projectSettings":case"localSettings":{const e=getSettingsRootPathForSource(t),s=n(e,getPreferredRelativeSettingsFilePathForSource(t)),o=n(e,getRelativeSettingsFilePathForSource(t)),r=P();return r.existsSync(s)?s:r.existsSync(o)?o:s}case"policySettings":return getManagedSettingsFilePath();case"flagSettings":return a()}}export function getRelativeSettingsFilePathForSource(t){switch(t){case"projectSettings":return n(".claude","settings.json");case"localSettings":return n(".claude","settings.local.json")}}export function getPreferredRelativeSettingsFilePathForSource(t){switch(t){case"projectSettings":return n(".context","settings.json");case"localSettings":return n(".context","settings.local.json")}}export function getProjectRelativeSettingsPathForDisplay(t){const e=getPreferredRelativeSettingsFilePathForSource(t),s=getRelativeSettingsFilePathForSource(t),o=getSettingsRootPathForSource(t),r=P();return r.existsSync(n(o,e))?e:r.existsSync(n(o,s))?s:e}export function getSettingsForSource(t){const e=T(t);if(void 0!==e)return e;const s=getSettingsForSourceUncached(t);return L(t,s),s}function getSettingsForSourceUncached(t){if("policySettings"===t){const t=l();if(t&&Object.keys(t).length>0)return t;const e=I();if(Object.keys(e.settings).length>0)return e.settings;const{settings:s}=loadManagedFileSettings();if(s)return s;const n=_();return Object.keys(n.settings).length>0?n.settings:null}const s=getSettingsFilePathForSource(t),{settings:n}=s?parseSettingsFile(s):{settings:null};if("flagSettings"===t){const t=i();if(t){const s=B().safeParse(t);if(s.success)return e(n||{},s.data,settingsMergeCustomizer)}}return n}export function getPolicySettingsOrigin(){const t=l();if(t&&Object.keys(t).length>0)return"remote";const e=I();if(Object.keys(e.settings).length>0)return"macos"===x()?"plist":"hklm";const{settings:s}=loadManagedFileSettings();if(s)return"file";const n=_();return Object.keys(n.settings).length>0?"hkcu":null}export function updateSettingsForSource(t,n){if("policySettings"===t||"flagSettings"===t)return{error:null};const o=getSettingsFilePathForSource(t);if(!o)return{error:null};try{P().mkdirSync(s(o));let r=getSettingsForSourceUncached(t);if(!r){let t=null;try{t=F(o)}catch(t){if(!h(t))throw t}if(null!==t){const e=b(t);if(null===e)return{error:new Error(`Invalid JSON syntax in settings file at ${o}`)};e&&"object"==typeof e&&(r=e,S(`Using raw settings from ${o} due to validation failure`))}}const i=e(r||{},n,(t,e,s,n)=>{if(void 0!==e||!n||"string"!=typeof s)return Array.isArray(e)?e:void 0;delete n[s]});R(o),y(o,v(i,null,2)+"\n"),N(),"localSettings"===t&&(k(getRelativeSettingsFilePathForSource("localSettings"),g()),k(getPreferredRelativeSettingsFilePathForSource("localSettings"),g()))}catch(t){const e=new Error(`Failed to read raw settings from ${o}: ${t}`);return M(e),{error:e}}return{error:null}}export function settingsMergeCustomizer(t,e){if(Array.isArray(t)&&Array.isArray(e))return s=t,n=e,u([...s,...n]);var s,n}export function getManagedSettingsKeysForLogging(e){const s=B().strip().parse(e),n=["permissions","sandbox","hooks"],o=[],r={permissions:new Set(["allow","deny","ask","defaultMode","disableBypassPermissionsMode",...t("TRANSCRIPT_CLASSIFIER")?["disableAutoMode"]:[],"additionalDirectories"]),sandbox:new Set(["enabled","failIfUnavailable","allowUnsandboxedCommands","network","filesystem","ignoreViolations","excludedCommands","autoAllowBashIfSandboxed","enableWeakerNestedSandbox","enableWeakerNetworkIsolation","ripgrep"]),hooks:new Set(["PreToolUse","PostToolUse","Notification","UserPromptSubmit","SessionStart","SessionEnd","Stop","SubagentStop","PreCompact","PostCompact","TeammateIdle","TaskCreated","TaskCompleted"])};for(const t of Object.keys(s))if(n.includes(t)&&s[t]&&"object"==typeof s[t]){const e=s[t],n=r[t];if(n)for(const s of Object.keys(e))n.has(s)&&o.push(`${t}.${s}`)}else o.push(t);return o.sort()}let J=!1;export function getInitialSettings(){const{settings:t}=getSettingsWithErrors();return t||{}}export const getSettings_DEPRECATED=getInitialSettings;export function getSettingsWithSources(){N();const t=[];for(const e of O()){const s=getSettingsForSource(e);s&&Object.keys(s).length>0&&t.push({source:e,settings:s})}return{effective:getInitialSettings(),sources:t}}export function getSettingsWithErrors(){const t=U();if(null!==t)return t;const s=function(){if(J)return{settings:{},errors:[]};const t=Date.now();A("loadSettingsFromDisk_start"),f("info","settings_load_started"),J=!0;try{const s=$();let n={};s&&(n=e(n,s,settingsMergeCustomizer));const r=[],a=new Set,g=new Set;for(const t of O()){if("policySettings"===t){let t=null;const s=[],o=l();if(o&&Object.keys(o).length>0){const e=B().safeParse(o);e.success?t=e.data:s.push(...G(e.error,"remote managed settings"))}if(!t){const e=I();Object.keys(e.settings).length>0&&(t=e.settings),s.push(...e.errors)}if(!t){const{settings:e,errors:n}=loadManagedFileSettings();e&&(t=e),s.push(...n)}if(!t){const e=_();Object.keys(e.settings).length>0&&(t=e.settings),s.push(...e.errors)}t&&(n=e(n,t,settingsMergeCustomizer));for(const t of s){const e=`${t.file}:${t.path}:${t.message}`;a.has(e)||(a.add(e),r.push(t))}continue}const s=getSettingsFilePathForSource(t);if(s){const t=o(s);if(!g.has(t)){g.add(t);const{settings:o,errors:i}=parseSettingsFile(s);for(const t of i){const e=`${t.file}:${t.path}:${t.message}`;a.has(e)||(a.add(e),r.push(t))}o&&(n=e(n,o,settingsMergeCustomizer))}}if("flagSettings"===t){const t=i();if(t){const s=B().safeParse(t);s.success&&(n=e(n,s.data,settingsMergeCustomizer))}}}return f("info","settings_load_completed",{duration_ms:Date.now()-t,source_count:g.size,error_count:r.length}),{settings:n,errors:r}}finally{J=!1}}();return A("loadSettingsFromDisk_end"),z(s),s}export function hasSkipDangerousModePermissionPrompt(){return!!(getSettingsForSource("userSettings")?.skipDangerousModePermissionPrompt||getSettingsForSource("localSettings")?.skipDangerousModePermissionPrompt||getSettingsForSource("flagSettings")?.skipDangerousModePermissionPrompt||getSettingsForSource("policySettings")?.skipDangerousModePermissionPrompt)}export function hasAutoModeOptIn(){if(t("TRANSCRIPT_CLASSIFIER")){const t=getSettingsForSource("userSettings")?.skipAutoPermissionPrompt,e=getSettingsForSource("localSettings")?.skipAutoPermissionPrompt,s=getSettingsForSource("flagSettings")?.skipAutoPermissionPrompt,n=getSettingsForSource("policySettings")?.skipAutoPermissionPrompt,o=!!(t||e||s||n);return S(`[auto-mode] hasAutoModeOptIn=${o} skipAutoPermissionPrompt: user=${t} local=${e} flag=${s} policy=${n}`),o}return!1}export function getUseAutoModeDuringPlan(){return!t("TRANSCRIPT_CLASSIFIER")||!1!==getSettingsForSource("policySettings")?.useAutoModeDuringPlan&&!1!==getSettingsForSource("flagSettings")?.useAutoModeDuringPlan&&!1!==getSettingsForSource("userSettings")?.useAutoModeDuringPlan&&!1!==getSettingsForSource("localSettings")?.useAutoModeDuringPlan}export function getAutoModeConfig(){if(t("TRANSCRIPT_CLASSIFIER")){const t=r.object({allow:r.array(r.string()).optional(),soft_deny:r.array(r.string()).optional(),deny:r.array(r.string()).optional(),environment:r.array(r.string()).optional()}),e=[],s=[],n=[];for(const o of["userSettings","localSettings","flagSettings","policySettings"]){const r=getSettingsForSource(o);if(!r)continue;const i=t.safeParse(r.autoMode);i.success&&(i.data.allow&&e.push(...i.data.allow),i.data.soft_deny&&s.push(...i.data.soft_deny),"ant"===process.env.USER_TYPE&&i.data.deny&&s.push(...i.data.deny),i.data.environment&&n.push(...i.data.environment))}if(e.length>0||s.length>0||n.length>0)return{...e.length>0&&{allow:e},...s.length>0&&{soft_deny:s},...n.length>0&&{environment:n}}}}export function rawSettingsContainsKey(t){for(const e of O()){if("policySettings"===e)continue;const s=getSettingsFilePathForSource(e);if(s)try{const{resolvedPath:e}=j(P(),s),n=F(e);if(!n.trim())continue;const o=b(n,!1);if(o&&"object"==typeof o&&t in o)return!0}catch(t){handleFileSystemError(t,s)}}return!1}
1
+ import{feature as t}from"../../recovery/bunBundleShim.js";import e from"lodash-es/mergeWith.js";import{dirname as s,join as n,resolve as o}from"path";import{z as r}from"zod/v4";import{getFlagSettingsInline as i,getFlagSettingsPath as a,getOriginalCwd as g,getUseCoworkPlugins as c}from"../../bootstrap/state.js";import{getRemoteManagedSettingsSyncFromCache as l}from"../../services/remoteManagedSettings/syncCacheState.js";import{uniq as u}from"../array.js";import{logForDebugging as S}from"../debug.js";import{logForDiagnosticsNoPII as f}from"../diagLogs.js";import{getClaudeConfigHomeDir as p,isEnvTruthy as m}from"../envUtils.js";import{getErrnoCode as d,isENOENT as h}from"../errors.js";import{writeFileSyncAndFlush_DEPRECATED as y}from"../file.js";import{readFileSync as F}from"../fileRead.js";import{getFsImplementation as P,safeResolvePath as j}from"../fsOperations.js";import{addFileGlobRuleToGitignore as k}from"../git/gitignore.js";import{safeParseJSON as b}from"../json.js";import{logError as M}from"../log.js";import{getPlatform as x}from"../platform.js";import{clone as C,jsonStringify as v}from"../slowOperations.js";import{profileCheckpoint as A}from"../startupProfiler.js";import{getEnabledSettingSources as O}from"./constants.js";import{markInternalWrite as R}from"./internalWrites.js";import{getManagedFilePath as w,getManagedSettingsDropInDir as E}from"./managedPath.js";import{getHkcuSettings as _,getMdmSettings as I}from"./mdm/settings.js";import{getCachedParsedFile as D,getCachedSettingsForSource as T,getPluginSettingsBase as $,getSessionSettingsCache as U,resetSettingsCache as N,setCachedParsedFile as W,setCachedSettingsForSource as L,setSessionSettingsCache as z}from"./settingsCache.js";import{SettingsSchema as B}from"./types.js";import{filterInvalidPermissionRules as K,formatZodError as G}from"./validation.js";function getManagedSettingsFilePath(){return n(w(),"managed-settings.json")}export function loadManagedFileSettings(){const t=[];let s={},o=!1;const{settings:r,errors:i}=parseSettingsFile(getManagedSettingsFilePath());t.push(...i),r&&Object.keys(r).length>0&&(s=e(s,r,settingsMergeCustomizer),o=!0);const a=E();try{const r=P().readdirSync(a).filter(t=>(t.isFile()||t.isSymbolicLink())&&t.name.endsWith(".json")&&!t.name.startsWith(".")).map(t=>t.name).sort();for(const i of r){const{settings:r,errors:g}=parseSettingsFile(n(a,i));t.push(...g),r&&Object.keys(r).length>0&&(s=e(s,r,settingsMergeCustomizer),o=!0)}}catch(t){const e=d(t);"ENOENT"!==e&&"ENOTDIR"!==e&&M(t)}return{settings:o?s:null,errors:t}}export function getManagedFileSettingsPresence(){const{settings:t}=parseSettingsFile(getManagedSettingsFilePath()),e=!!t&&Object.keys(t).length>0;let s=!1;const n=E();try{s=P().readdirSync(n).some(t=>(t.isFile()||t.isSymbolicLink())&&t.name.endsWith(".json")&&!t.name.startsWith("."))}catch{}return{hasBase:e,hasDropIns:s}}function handleFileSystemError(t,e){"object"==typeof t&&t&&"code"in t&&"ENOENT"===t.code?S(`Broken symlink or missing file encountered for settings.json at path: ${e}`):M(t)}export function parseSettingsFile(t){const e=D(t);if(e)return{settings:e.settings?C(e.settings):null,errors:e.errors};const s=function(t){try{const{resolvedPath:e}=j(P(),t),s=F(e);if(""===s.trim())return{settings:{},errors:[]};const n=b(s,!1),o=K(n,t),r=B().safeParse(n);if(!r.success){const e=G(r.error,t);return{settings:null,errors:[...o,...e]}}return{settings:r.data,errors:o}}catch(e){return handleFileSystemError(e,t),{settings:null,errors:[]}}}(t);return W(t,s),{settings:s.settings?C(s.settings):null,errors:s.errors}}export function getSettingsRootPathForSource(t){switch(t){case"userSettings":return o(p());case"policySettings":case"projectSettings":case"localSettings":return o(g());case"flagSettings":{const t=a();return t?s(o(t)):o(g())}}}export function getSettingsFilePathForSource(t){switch(t){case"userSettings":return n(getSettingsRootPathForSource(t),c()||m(process.env.CONTEXT_CODE_USE_COWORK_PLUGINS)||m(process.env.CLAUDE_CODE_USE_COWORK_PLUGINS)?"cowork_settings.json":"settings.json");case"projectSettings":case"localSettings":{const e=getSettingsRootPathForSource(t),s=n(e,getPreferredRelativeSettingsFilePathForSource(t)),o=n(e,getRelativeSettingsFilePathForSource(t)),r=P();return r.existsSync(s)?s:r.existsSync(o)?o:s}case"policySettings":return getManagedSettingsFilePath();case"flagSettings":return a()}}export function getRelativeSettingsFilePathForSource(t){switch(t){case"projectSettings":return n(".claude","settings.json");case"localSettings":return n(".claude","settings.local.json")}}export function getPreferredRelativeSettingsFilePathForSource(t){switch(t){case"projectSettings":return n(".contextcli","settings.json");case"localSettings":return n(".contextcli","settings.local.json")}}export function getProjectRelativeSettingsPathForDisplay(t){const e=getPreferredRelativeSettingsFilePathForSource(t),s=getRelativeSettingsFilePathForSource(t),o=getSettingsRootPathForSource(t),r=P();return r.existsSync(n(o,e))?e:r.existsSync(n(o,s))?s:e}export function getSettingsForSource(t){const e=T(t);if(void 0!==e)return e;const s=getSettingsForSourceUncached(t);return L(t,s),s}function getSettingsForSourceUncached(t){if("policySettings"===t){const t=l();if(t&&Object.keys(t).length>0)return t;const e=I();if(Object.keys(e.settings).length>0)return e.settings;const{settings:s}=loadManagedFileSettings();if(s)return s;const n=_();return Object.keys(n.settings).length>0?n.settings:null}const s=getSettingsFilePathForSource(t),{settings:n}=s?parseSettingsFile(s):{settings:null};if("flagSettings"===t){const t=i();if(t){const s=B().safeParse(t);if(s.success)return e(n||{},s.data,settingsMergeCustomizer)}}return n}export function getPolicySettingsOrigin(){const t=l();if(t&&Object.keys(t).length>0)return"remote";const e=I();if(Object.keys(e.settings).length>0)return"macos"===x()?"plist":"hklm";const{settings:s}=loadManagedFileSettings();if(s)return"file";const n=_();return Object.keys(n.settings).length>0?"hkcu":null}export function updateSettingsForSource(t,n){if("policySettings"===t||"flagSettings"===t)return{error:null};const o=getSettingsFilePathForSource(t);if(!o)return{error:null};try{P().mkdirSync(s(o));let r=getSettingsForSourceUncached(t);if(!r){let t=null;try{t=F(o)}catch(t){if(!h(t))throw t}if(null!==t){const e=b(t);if(null===e)return{error:new Error(`Invalid JSON syntax in settings file at ${o}`)};e&&"object"==typeof e&&(r=e,S(`Using raw settings from ${o} due to validation failure`))}}const i=e(r||{},n,(t,e,s,n)=>{if(void 0!==e||!n||"string"!=typeof s)return Array.isArray(e)?e:void 0;delete n[s]});R(o),y(o,v(i,null,2)+"\n"),N(),"localSettings"===t&&(k(getRelativeSettingsFilePathForSource("localSettings"),g()),k(getPreferredRelativeSettingsFilePathForSource("localSettings"),g()))}catch(t){const e=new Error(`Failed to read raw settings from ${o}: ${t}`);return M(e),{error:e}}return{error:null}}export function settingsMergeCustomizer(t,e){if(Array.isArray(t)&&Array.isArray(e))return s=t,n=e,u([...s,...n]);var s,n}export function getManagedSettingsKeysForLogging(e){const s=B().strip().parse(e),n=["permissions","sandbox","hooks"],o=[],r={permissions:new Set(["allow","deny","ask","defaultMode","disableBypassPermissionsMode",...t("TRANSCRIPT_CLASSIFIER")?["disableAutoMode"]:[],"additionalDirectories"]),sandbox:new Set(["enabled","failIfUnavailable","allowUnsandboxedCommands","network","filesystem","ignoreViolations","excludedCommands","autoAllowBashIfSandboxed","enableWeakerNestedSandbox","enableWeakerNetworkIsolation","ripgrep"]),hooks:new Set(["PreToolUse","PostToolUse","Notification","UserPromptSubmit","SessionStart","SessionEnd","Stop","SubagentStop","PreCompact","PostCompact","TeammateIdle","TaskCreated","TaskCompleted"])};for(const t of Object.keys(s))if(n.includes(t)&&s[t]&&"object"==typeof s[t]){const e=s[t],n=r[t];if(n)for(const s of Object.keys(e))n.has(s)&&o.push(`${t}.${s}`)}else o.push(t);return o.sort()}let J=!1;export function getInitialSettings(){const{settings:t}=getSettingsWithErrors();return t||{}}export const getSettings_DEPRECATED=getInitialSettings;export function getSettingsWithSources(){N();const t=[];for(const e of O()){const s=getSettingsForSource(e);s&&Object.keys(s).length>0&&t.push({source:e,settings:s})}return{effective:getInitialSettings(),sources:t}}export function getSettingsWithErrors(){const t=U();if(null!==t)return t;const s=function(){if(J)return{settings:{},errors:[]};const t=Date.now();A("loadSettingsFromDisk_start"),f("info","settings_load_started"),J=!0;try{const s=$();let n={};s&&(n=e(n,s,settingsMergeCustomizer));const r=[],a=new Set,g=new Set;for(const t of O()){if("policySettings"===t){let t=null;const s=[],o=l();if(o&&Object.keys(o).length>0){const e=B().safeParse(o);e.success?t=e.data:s.push(...G(e.error,"remote managed settings"))}if(!t){const e=I();Object.keys(e.settings).length>0&&(t=e.settings),s.push(...e.errors)}if(!t){const{settings:e,errors:n}=loadManagedFileSettings();e&&(t=e),s.push(...n)}if(!t){const e=_();Object.keys(e.settings).length>0&&(t=e.settings),s.push(...e.errors)}t&&(n=e(n,t,settingsMergeCustomizer));for(const t of s){const e=`${t.file}:${t.path}:${t.message}`;a.has(e)||(a.add(e),r.push(t))}continue}const s=getSettingsFilePathForSource(t);if(s){const t=o(s);if(!g.has(t)){g.add(t);const{settings:o,errors:i}=parseSettingsFile(s);for(const t of i){const e=`${t.file}:${t.path}:${t.message}`;a.has(e)||(a.add(e),r.push(t))}o&&(n=e(n,o,settingsMergeCustomizer))}}if("flagSettings"===t){const t=i();if(t){const s=B().safeParse(t);s.success&&(n=e(n,s.data,settingsMergeCustomizer))}}}return f("info","settings_load_completed",{duration_ms:Date.now()-t,source_count:g.size,error_count:r.length}),{settings:n,errors:r}}finally{J=!1}}();return A("loadSettingsFromDisk_end"),z(s),s}export function hasSkipDangerousModePermissionPrompt(){return!!(getSettingsForSource("userSettings")?.skipDangerousModePermissionPrompt||getSettingsForSource("localSettings")?.skipDangerousModePermissionPrompt||getSettingsForSource("flagSettings")?.skipDangerousModePermissionPrompt||getSettingsForSource("policySettings")?.skipDangerousModePermissionPrompt)}export function hasAutoModeOptIn(){if(t("TRANSCRIPT_CLASSIFIER")){const t=getSettingsForSource("userSettings")?.skipAutoPermissionPrompt,e=getSettingsForSource("localSettings")?.skipAutoPermissionPrompt,s=getSettingsForSource("flagSettings")?.skipAutoPermissionPrompt,n=getSettingsForSource("policySettings")?.skipAutoPermissionPrompt,o=!!(t||e||s||n);return S(`[auto-mode] hasAutoModeOptIn=${o} skipAutoPermissionPrompt: user=${t} local=${e} flag=${s} policy=${n}`),o}return!1}export function getUseAutoModeDuringPlan(){return!t("TRANSCRIPT_CLASSIFIER")||!1!==getSettingsForSource("policySettings")?.useAutoModeDuringPlan&&!1!==getSettingsForSource("flagSettings")?.useAutoModeDuringPlan&&!1!==getSettingsForSource("userSettings")?.useAutoModeDuringPlan&&!1!==getSettingsForSource("localSettings")?.useAutoModeDuringPlan}export function getAutoModeConfig(){if(t("TRANSCRIPT_CLASSIFIER")){const t=r.object({allow:r.array(r.string()).optional(),soft_deny:r.array(r.string()).optional(),deny:r.array(r.string()).optional(),environment:r.array(r.string()).optional()}),e=[],s=[],n=[];for(const o of["userSettings","localSettings","flagSettings","policySettings"]){const r=getSettingsForSource(o);if(!r)continue;const i=t.safeParse(r.autoMode);i.success&&(i.data.allow&&e.push(...i.data.allow),i.data.soft_deny&&s.push(...i.data.soft_deny),"ant"===process.env.USER_TYPE&&i.data.deny&&s.push(...i.data.deny),i.data.environment&&n.push(...i.data.environment))}if(e.length>0||s.length>0||n.length>0)return{...e.length>0&&{allow:e},...s.length>0&&{soft_deny:s},...n.length>0&&{environment:n}}}}export function rawSettingsContainsKey(t){for(const e of O()){if("policySettings"===e)continue;const s=getSettingsFilePathForSource(e);if(s)try{const{resolvedPath:e}=j(P(),s),n=F(e);if(!n.trim())continue;const o=b(n,!1);if(o&&"object"==typeof o&&t in o)return!0}catch(t){handleFileSystemError(t,s)}}return!1}
@@ -1 +1 @@
1
- import t from"chokidar";import*as e from"path";import{getAdditionalDirectoriesForClaudeMd as s}from"../../bootstrap/state.js";import{clearCommandMemoizationCaches as i,clearCommandsCache as o}from"../../commands.js";import{logEvent as n}from"../../services/analytics/index.js";import{clearSkillCaches as r,getSkillsPath as l,onDynamicSkillsLoaded as a}from"../../skills/loadSkillsDir.js";import{resetSentSkillNames as c}from"../attachments.js";import{registerCleanup as u}from"../cleanupRegistry.js";import{logForDebugging as m}from"../debug.js";import{getFsImplementation as h}from"../fsOperations.js";import{executeConfigChangeHooks as p,hasBlockingResult as d}from"../hooks.js";import{createSignal as f}from"../signal.js";const g="undefined"!=typeof Bun;let k=null,y=null;const b=new Set;let j=!1,w=!1,T=!1,v=null;const x=f();let C=null;export async function initialize(){if(j||w)return;j=!0,T||(T=!0,a(()=>{i(),x.emit()}));const o=await async function(){const t=h(),i=[],o=l("userSettings","skills");if(o)try{await t.stat(o),i.push(o)}catch{}const n=l("userSettings","commands");if(n)try{await t.stat(n),i.push(n)}catch{}const r=l("projectSettings","skills");if(r)try{const s=e.resolve(r);await t.stat(s),i.push(s)}catch{}const a=l("projectSettings","commands");if(a)try{const s=e.resolve(a);await t.stat(s),i.push(s)}catch{}for(const o of s())for(const s of[".context",".claude"]){const n=e.join(o,s,"skills");try{await t.stat(n),i.push(n)}catch{}}return i}();0!==o.length&&(m(`Watching for changes in skill/command directories: ${o.join(", ")}...`),k=t.watch(o,{persistent:!0,ignoreInitial:!0,depth:2,awaitWriteFinish:{stabilityThreshold:C?.stabilityThreshold??1e3,pollInterval:C?.pollInterval??500},ignored:(t,s)=>!(!s||s.isFile()||s.isDirectory())||t.split(e.sep).some(t=>".git"===t),ignorePermissionErrors:!0,usePolling:g,interval:C?.chokidarInterval??2e3,atomic:!0}),k.on("add",handleChange),k.on("change",handleChange),k.on("unlink",handleChange),v=u(async()=>{await dispose()}))}export function dispose(){w=!0,v&&(v(),v=null);let t=Promise.resolve();return k&&(t=k.close(),k=null),y&&(clearTimeout(y),y=null),b.clear(),x.clear(),t}export const subscribe=x.subscribe;function handleChange(t){m(`Detected skill change: ${t}`),n("tengu_skill_file_changed",{source:"chokidar"}),function(t){b.add(t),y&&clearTimeout(y);y=setTimeout(async()=>{y=null;const t=[...b];b.clear();const e=await p("skills",t[0]);d(e)?m(`ConfigChange hook blocked skill reload (${t.length} paths)`):(r(),o(),c(),x.emit())},C?.reloadDebounce??300)}(t)}export async function resetForTesting(t){k&&(await k.close(),k=null),y&&(clearTimeout(y),y=null),b.clear(),x.clear(),j=!1,w=!1,C=t??null}export const skillChangeDetector={initialize,dispose,subscribe,resetForTesting};
1
+ import t from"chokidar";import*as e from"path";import{getAdditionalDirectoriesForClaudeMd as s}from"../../bootstrap/state.js";import{clearCommandMemoizationCaches as i,clearCommandsCache as o}from"../../commands.js";import{logEvent as n}from"../../services/analytics/index.js";import{clearSkillCaches as r,getSkillsPath as l,onDynamicSkillsLoaded as a}from"../../skills/loadSkillsDir.js";import{resetSentSkillNames as c}from"../attachments.js";import{registerCleanup as u}from"../cleanupRegistry.js";import{logForDebugging as m}from"../debug.js";import{getFsImplementation as h}from"../fsOperations.js";import{executeConfigChangeHooks as p,hasBlockingResult as d}from"../hooks.js";import{createSignal as f}from"../signal.js";const g="undefined"!=typeof Bun;let k=null,y=null;const b=new Set;let j=!1,w=!1,T=!1,v=null;const x=f();let C=null;export async function initialize(){if(j||w)return;j=!0,T||(T=!0,a(()=>{i(),x.emit()}));const o=await async function(){const t=h(),i=[],o=l("userSettings","skills");if(o)try{await t.stat(o),i.push(o)}catch{}const n=l("userSettings","commands");if(n)try{await t.stat(n),i.push(n)}catch{}const r=l("projectSettings","skills");if(r)try{const s=e.resolve(r);await t.stat(s),i.push(s)}catch{}const a=l("projectSettings","commands");if(a)try{const s=e.resolve(a);await t.stat(s),i.push(s)}catch{}for(const o of s())for(const s of[".contextcli",".claude"]){const n=e.join(o,s,"skills");try{await t.stat(n),i.push(n)}catch{}}return i}();0!==o.length&&(m(`Watching for changes in skill/command directories: ${o.join(", ")}...`),k=t.watch(o,{persistent:!0,ignoreInitial:!0,depth:2,awaitWriteFinish:{stabilityThreshold:C?.stabilityThreshold??1e3,pollInterval:C?.pollInterval??500},ignored:(t,s)=>!(!s||s.isFile()||s.isDirectory())||t.split(e.sep).some(t=>".git"===t),ignorePermissionErrors:!0,usePolling:g,interval:C?.chokidarInterval??2e3,atomic:!0}),k.on("add",handleChange),k.on("change",handleChange),k.on("unlink",handleChange),v=u(async()=>{await dispose()}))}export function dispose(){w=!0,v&&(v(),v=null);let t=Promise.resolve();return k&&(t=k.close(),k=null),y&&(clearTimeout(y),y=null),b.clear(),x.clear(),t}export const subscribe=x.subscribe;function handleChange(t){m(`Detected skill change: ${t}`),n("tengu_skill_file_changed",{source:"chokidar"}),function(t){b.add(t),y&&clearTimeout(y);y=setTimeout(async()=>{y=null;const t=[...b];b.clear();const e=await p("skills",t[0]);d(e)?m(`ConfigChange hook blocked skill reload (${t.length} paths)`):(r(),o(),c(),x.emit())},C?.reloadDebounce??300)}(t)}export async function resetForTesting(t){k&&(await k.close(),k=null),y&&(clearTimeout(y),y=null),b.clear(),x.clear(),j=!1,w=!1,C=t??null}export const skillChangeDetector={initialize,dispose,subscribe,resetForTesting};
@@ -1 +1 @@
1
- import{feature as e}from"../recovery/bunBundleShim.js";import t from"chalk";import{spawnSync as r}from"child_process";import{copyFile as o,mkdir as n,readdir as a,readFile as s,stat as i,symlink as c,utimes as l}from"fs/promises";import d from"ignore";import{basename as w,dirname as u,join as m}from"path";import{saveCurrentProjectConfig as h}from"./config.js";import{getCwd as f}from"./cwd.js";import{logForDebugging as k}from"./debug.js";import{errorMessage as p,getErrnoCode as g}from"./errors.js";import{execFileNoThrow as $,execFileNoThrowWithCwd as x}from"./execFileNoThrow.js";import{parseGitConfigValue as v}from"./git/gitConfigParser.js";import{getCommonDir as C,readWorktreeHeadSha as y,resolveGitDir as b,resolveRef as W}from"./git/gitFilesystem.js";import{findCanonicalGitRoot as P,findGitRoot as S,getBranch as E,getDefaultBranch as T,gitExe as I}from"./git.js";import{executeWorktreeCreateHook as F,executeWorktreeRemoveHook as A,hasWorktreeCreateHook as _}from"./hooks.js";import{containsPathTraversal as D}from"./path.js";import{getPlatform as B}from"./platform.js";import{getInitialSettings as N,getRelativeSettingsFilePathForSource as R}from"./settings/settings.js";import{sleep as j}from"./sleep.js";import{isInITerm2 as M}from"./swarm/backends/detection.js";const O=/^[a-zA-Z0-9._-]+$/;export function validateWorktreeSlug(e){if(e.length>64)throw new Error(`Invalid worktree name: must be 64 characters or fewer (got ${e.length})`);for(const t of e.split("/")){if("."===t||".."===t)throw new Error(`Invalid worktree name "${e}": must not contain "." or ".." path segments`);if(!O.test(t))throw new Error(`Invalid worktree name "${e}": each "/"-separated segment must be non-empty and contain only letters, digits, dots, underscores, and dashes`)}}let U=null;export function getCurrentWorktreeSession(){return U}export function restoreWorktreeSession(e){U=e}export function generateTmuxSessionName(e,t){return`${w(e)}_${t}`.replace(/[/.]/g,"_")}const H={GIT_TERMINAL_PROMPT:"0",GIT_ASKPASS:""};function worktreesDir(e){return m(e,".context","worktrees")}function flattenSlug(e){return e.replaceAll("/","+")}export function worktreeBranchName(e){return`worktree-${flattenSlug(e)}`}function worktreePathFor(e,t){return m(worktreesDir(e),flattenSlug(t))}async function getOrCreateWorktree(e,t,r){const o=worktreePathFor(e,t),a=worktreeBranchName(t),s=await y(o);if(s)return{worktreePath:o,worktreeBranch:a,headCommit:s,existed:!0};await n(worktreesDir(e),{recursive:!0});const i={...process.env,...H};let c,l=null;if(r?.prNumber){const{code:t,stderr:o}=await x(I(),["fetch","origin",`pull/${r.prNumber}/head`],{cwd:e,stdin:"ignore",env:i});if(0!==t)throw new Error(`Failed to fetch PR #${r.prNumber}: ${o.trim()||'PR may not exist or the repository may not have a remote named "origin"'}`);c="FETCH_HEAD"}else{const[t,r]=await Promise.all([T(),b(e)]),o=`origin/${t}`,n=r?await W(r,`refs/remotes/origin/${t}`):null;if(n)c=o,l=n;else{const{code:r}=await x(I(),["fetch","origin",t],{cwd:e,stdin:"ignore",env:i});c=0===r?o:"HEAD"}}if(!l){const{stdout:t,code:r}=await x(I(),["rev-parse",c],{cwd:e});if(0!==r)throw new Error(`Failed to resolve base branch "${c}": git rev-parse failed`);l=t.trim()}const d=N().worktree?.sparsePaths,w=["worktree","add"];d?.length&&w.push("--no-checkout"),w.push("-B",a,o,c);const{code:u,stderr:m}=await x(I(),w,{cwd:e});if(0!==u)throw new Error(`Failed to create worktree: ${m}`);if(d?.length){const tearDown=async t=>{throw await x(I(),["worktree","remove","--force",o],{cwd:e}),new Error(t)},{code:t,stderr:r}=await x(I(),["sparse-checkout","set","--cone","--",...d],{cwd:o});0!==t&&await tearDown(`Failed to configure sparse-checkout: ${r}`);const{code:n,stderr:a}=await x(I(),["checkout","HEAD"],{cwd:o});0!==n&&await tearDown(`Failed to checkout sparse worktree: ${a}`)}return{worktreePath:o,worktreeBranch:a,headCommit:l,baseBranch:c,existed:!1}}export async function copyWorktreeIncludeFiles(e,t){let r;try{r=await s(m(e,".worktreeinclude"),"utf-8")}catch{return[]}const a=r.split(/\r?\n/).map(e=>e.trim()).filter(e=>e.length>0&&!e.startsWith("#"));if(0===a.length)return[];const i=await x(I(),["ls-files","--others","--ignored","--exclude-standard","--directory"],{cwd:e});if(0!==i.code||!i.stdout.trim())return[];const c=i.stdout.trim().split("\n").filter(Boolean),l=d().add(r),w=c.filter(e=>e.endsWith("/")),h=c.filter(e=>!e.endsWith("/")&&l.ignores(e)),f=w.filter(e=>!!a.some(t=>{const r=t.startsWith("/")?t.slice(1):t;if(r.startsWith(e))return!0;const o=r.search(/[*?[]/);if(o>0){const t=r.slice(0,o);if(e.startsWith(t))return!0}return!1})||!!l.ignores(e.slice(0,-1)));if(f.length>0){const t=await x(I(),["ls-files","--others","--ignored","--exclude-standard","--",...f],{cwd:e});if(0===t.code&&t.stdout.trim())for(const e of t.stdout.trim().split("\n").filter(Boolean))l.ignores(e)&&h.push(e)}const p=[];for(const r of h){const a=m(e,r),s=m(t,r);try{await n(u(s),{recursive:!0}),await o(a,s),p.push(r)}catch(e){k(`Failed to copy ${r} to worktree: ${e.message}`,{level:"warn"})}}return p.length>0&&k(`Copied ${p.length} files from .worktreeinclude: ${p.join(", ")}`),p}async function performPostCreationSetup(t,r){const a=R("localSettings"),s=m(t,a);try{const e=m(r,a);await async function(e){await n(e,{recursive:!0})}(u(e)),await o(s,e),k(`Copied settings.local.json to worktree: ${e}`)}catch(e){"ENOENT"!==g(e)&&k(`Failed to copy settings.local.json: ${e.message}`,{level:"warn"})}const l=m(t,".husky"),d=m(t,".git","hooks");let w=null;for(const e of[l,d])try{if((await i(e)).isDirectory()){w=e;break}}catch{}if(w){const e=await b(t),o=e?await C(e)??e:null;if((o?await v(o,"core",null,"hooksPath"):null)!==w){const{code:e,stderr:t}=await x(I(),["config","core.hooksPath",w],{cwd:r});0===e?k(`Configured worktree to use hooks from main repository: ${w}`):k(`Failed to configure hooks path: ${t}`,{level:"error"})}}const h=N(),f=h.worktree?.symlinkDirectories??[];if(f.length>0&&await async function(e,t,r){for(const o of r){if(D(o)){k(`Skipping symlink for "${o}": path traversal detected`,{level:"warn"});continue}const r=m(e,o),n=m(t,o);try{await c(r,n,"dir"),k(`Symlinked ${o} from main repository to worktree to avoid disk bloat`)}catch(e){const t=g(e);"ENOENT"!==t&&"EEXIST"!==t&&k(`Failed to symlink ${o} (${t??"unknown"}): ${p(e)}`,{level:"warn"})}}}(t,r,f),await copyWorktreeIncludeFiles(t,r),e("COMMIT_ATTRIBUTION")){const e=w===l?m(r,".husky"):void 0;import("./postCommitAttribution.js").then(t=>t.installPrepareCommitMsgHook(r,e).catch(e=>{k(`Failed to install attribution hook in worktree: ${e}`)})).catch(e=>{k(`Failed to load postCommitAttribution module: ${e}`)})}}export function parsePRReference(e){const t=e.match(/^https?:\/\/[^/]+\/[^/]+\/[^/]+\/pull\/(\d+)\/?(?:[?#].*)?$/i);if(t?.[1])return parseInt(t[1],10);const r=e.match(/^#(\d+)$/);return r?.[1]?parseInt(r[1],10):null}export async function isTmuxAvailable(){const{code:e}=await $("tmux",["-V"]);return 0===e}export function getTmuxInstallInstructions(){switch(B()){case"macos":return"Install tmux with: brew install tmux";case"linux":case"wsl":return"Install tmux with: sudo apt install tmux (Debian/Ubuntu) or sudo dnf install tmux (Fedora/RHEL)";case"windows":return"tmux is not natively available on Windows. Consider using WSL or Cygwin.";default:return"Install tmux using your system package manager."}}export async function createTmuxSessionForWorktree(e,t){const{code:r,stderr:o}=await $("tmux",["new-session","-d","-s",e,"-c",t]);return 0!==r?{created:!1,error:o}:{created:!0}}export async function killTmuxSession(e){const{code:t}=await $("tmux",["kill-session","-t",e]);return 0===t}export async function createWorktreeForSession(e,t,r,o){validateWorktreeSlug(t);const n=f();if(_()){const o=await F(t);k(`Created hook-based worktree at: ${o.worktreePath}`),U={originalCwd:n,worktreePath:o.worktreePath,worktreeName:t,sessionId:e,tmuxSessionName:r,hookBased:!0}}else{const a=S(f());if(!a)throw new Error("Cannot create a worktree: not in a git repository and no WorktreeCreate hooks are configured. Configure WorktreeCreate/WorktreeRemove hooks in settings.json to use worktree isolation with other VCS systems.");const s=await E(),i=Date.now(),{worktreePath:c,worktreeBranch:l,headCommit:d,existed:w}=await getOrCreateWorktree(a,t,o);let u;w?k(`Resuming existing worktree at: ${c}`):(k(`Created worktree at: ${c} on branch: ${l}`),await performPostCreationSetup(a,c),u=Date.now()-i),U={originalCwd:n,worktreePath:c,worktreeName:t,worktreeBranch:l,originalBranch:s,originalHeadCommit:d,sessionId:e,tmuxSessionName:r,creationDurationMs:u,usedSparsePaths:(N().worktree?.sparsePaths?.length??0)>0}}return h(e=>({...e,activeWorktreeSession:U??void 0})),U}export async function keepWorktree(){if(U)try{const{worktreePath:e,originalCwd:t,worktreeBranch:r}=U;process.chdir(t),U=null,h(e=>({...e,activeWorktreeSession:void 0})),k(`Linked worktree preserved at: ${e}${r?` on branch: ${r}`:""}`),k(`You can continue working there by running: cd ${e}`)}catch(e){k(`Error keeping worktree: ${e}`,{level:"error"})}}export async function cleanupWorktree(){if(U)try{const{worktreePath:e,originalCwd:t,worktreeBranch:r,hookBased:o}=U;if(process.chdir(t),o){await A(e)?k(`Removed hook-based worktree at: ${e}`):k(`No WorktreeRemove hook configured, hook-based worktree left at: ${e}`,{level:"warn"})}else{const{code:r,stderr:o}=await x(I(),["worktree","remove","--force",e],{cwd:t});0!==r?k(`Failed to remove linked worktree: ${o}`,{level:"error"}):k(`Removed linked worktree at: ${e}`)}if(U=null,h(e=>({...e,activeWorktreeSession:void 0})),!o&&r){await j(100);const{code:e,stderr:o}=await x(I(),["branch","-D",r],{cwd:t});0!==e?k(`Could not delete worktree branch: ${o}`,{level:"error"}):k(`Deleted worktree branch: ${r}`)}k("Linked worktree cleaned up completely")}catch(e){k(`Error cleaning up worktree: ${e}`,{level:"error"})}}export async function createAgentWorktree(e){if(validateWorktreeSlug(e),_()){const t=await F(e);return k(`Created hook-based agent worktree at: ${t.worktreePath}`),{worktreePath:t.worktreePath,hookBased:!0}}const t=P(f());if(!t)throw new Error("Cannot create agent worktree: not in a git repository and no WorktreeCreate hooks are configured. Configure WorktreeCreate/WorktreeRemove hooks in settings.json to use worktree isolation with other VCS systems.");const{worktreePath:r,worktreeBranch:o,headCommit:n,existed:a}=await getOrCreateWorktree(t,e);if(a){const e=new Date;await l(r,e,e),k(`Resuming existing agent worktree at: ${r}`)}else k(`Created agent worktree at: ${r} on branch: ${o}`),await performPostCreationSetup(t,r);return{worktreePath:r,worktreeBranch:o,headCommit:n,gitRoot:t}}export async function removeAgentWorktree(e,t,r,o){if(o){const t=await A(e);return t?k(`Removed hook-based agent worktree at: ${e}`):k(`No WorktreeRemove hook configured, hook-based agent worktree left at: ${e}`,{level:"warn"}),t}if(!r)return k("Cannot remove agent worktree: no git root provided",{level:"error"}),!1;const{code:n,stderr:a}=await x(I(),["worktree","remove","--force",e],{cwd:r});if(0!==n)return k(`Failed to remove agent worktree: ${a}`,{level:"error"}),!1;if(k(`Removed agent worktree at: ${e}`),!t)return!0;const{code:s,stderr:i}=await x(I(),["branch","-D",t],{cwd:r});return 0!==s&&k(`Could not delete agent worktree branch: ${i}`,{level:"error"}),!0}const L=[/^agent-a[0-9a-f]{7}$/,/^wf_[0-9a-f]{8}-[0-9a-f]{3}-\d+$/,/^wf-\d+$/,/^bridge-[A-Za-z0-9_]+(-[A-Za-z0-9_]+)*$/,/^job-[a-zA-Z0-9._-]{1,55}-[0-9a-f]{8}$/];export async function cleanupStaleAgentWorktrees(e){const t=P(f());if(!t)return 0;const r=worktreesDir(t);let o;try{o=await a(r)}catch{return 0}const n=e.getTime(),s=U?.worktreePath;let c=0;for(const e of o){if(!L.some(t=>t.test(e)))continue;const o=m(r,e);if(s===o)continue;let a;try{a=(await i(o)).mtimeMs}catch{continue}if(a>=n)continue;const[l,d]=await Promise.all([x(I(),["--no-optional-locks","status","--porcelain","-uno"],{cwd:o}),x(I(),["rev-list","--max-count=1","HEAD","--not","--remotes"],{cwd:o})]);0!==l.code||l.stdout.trim().length>0||(0!==d.code||d.stdout.trim().length>0||await removeAgentWorktree(o,worktreeBranchName(e),t)&&c++)}return c>0&&(await x(I(),["worktree","prune"],{cwd:t}),k(`cleanupStaleAgentWorktrees: removed ${c} stale worktree(s)`)),c}export async function hasWorktreeChanges(e,t){const{code:r,stdout:o}=await x(I(),["status","--porcelain"],{cwd:e});if(0!==r)return!0;if(o.trim().length>0)return!0;const{code:n,stdout:a}=await x(I(),["rev-list","--count",`${t}..HEAD`],{cwd:e});return 0!==n||parseInt(a.trim(),10)>0}export async function execIntoTmuxWorktree(e){if("win32"===process.platform)return{handled:!1,error:"Error: --tmux is not supported on Windows"};if(0!==r("tmux",["-V"],{encoding:"utf-8"}).status){return{handled:!1,error:`Error: tmux is not installed. ${"darwin"===process.platform?"Install tmux with: brew install tmux":"Install tmux with: sudo apt install tmux"}`}}let o,n=!1;for(let t=0;t<e.length;t++){const r=e[t];if(r)if("-w"===r||"--worktree"===r){const r=e[t+1];r&&!r.startsWith("-")&&(o=r)}else r.startsWith("--worktree=")?o=r.slice(11):"--tmux=classic"===r&&(n=!0)}let a,s,i=null;if(o&&(i=parsePRReference(o),null!==i&&(o=`pr-${i}`)),!o){const e=["swift","bright","calm","keen","bold"],t=["fox","owl","elm","oak","ray"];o=`${e[Math.floor(Math.random()*e.length)]}-${t[Math.floor(Math.random()*t.length)]}-${Math.random().toString(36).slice(2,6)}`}try{validateWorktreeSlug(o)}catch(e){return{handled:!1,error:`Error: ${e.message}`}}if(_()){try{a=(await F(o)).worktreePath}catch(e){return{handled:!1,error:`Error: ${p(e)}`}}s=w(P(f())??f()),console.log(`Using worktree via hook: ${a}`)}else{const e=P(f());if(!e)return{handled:!1,error:"Error: --worktree requires a git repository"};s=w(e),a=worktreePathFor(e,o);try{const t=await getOrCreateWorktree(e,o,null!==i?{prNumber:i}:void 0);t.existed||(console.log(`Created worktree: ${a} (based on ${t.baseBranch})`),await performPostCreationSetup(e,a))}catch(e){return{handled:!1,error:`Error: ${p(e)}`}}}const c=`${s}_${worktreeBranchName(o)}`.replace(/[/.]/g,"_"),l=[];for(let t=0;t<e.length;t++){const r=e[t];if(r&&("--tmux"!==r&&"--tmux=classic"!==r)){if("-w"===r||"--worktree"===r){const r=e[t+1];r&&!r.startsWith("-")&&t++;continue}r.startsWith("--worktree=")||l.push(r)}}let d="C-b";const u=r("tmux",["show-options","-g","prefix"],{encoding:"utf-8"});if(0===u.status&&u.stdout){const e=u.stdout.match(/prefix\s+(\S+)/);e?.[1]&&(d=e[1])}const m=["C-b","C-c","C-d","C-t","C-o","C-r","C-s","C-g","C-e"].includes(d),h={...process.env,CLAUDE_CODE_TMUX_SESSION:c,CLAUDE_CODE_TMUX_PREFIX:d,CLAUDE_CODE_TMUX_PREFIX_CONFLICTS:m?"1":""},k=0===r("tmux",["has-session","-t",c],{encoding:"utf-8"}).status,g=Boolean(process.env.TMUX),$=M()&&!n&&!g,x=$?["-CC"]:[];if($&&!k){const e=t.yellow;console.log(`\n${e("╭─ iTerm2 Tip ────────────────────────────────────────────────────────╮")}\n${e("│")} To open as a tab instead of a new window: ${e("│")}\n${e("│")} iTerm2 > Settings > General > tmux > "Tabs in attaching window" ${e("│")}\n${e("╰─────────────────────────────────────────────────────────────────────╯")}\n`)}if("ant"===process.env.USER_TYPE&&"claude-cli-internal"===s&&!k)r("tmux",["new-session","-d","-s",c,"-c",a,"--",process.execPath,...l],{cwd:a,env:h}),r("tmux",["split-window","-h","-t",c,"-c",a],{cwd:a}),r("tmux",["send-keys","-t",c,"bun run watch","Enter"],{cwd:a}),r("tmux",["split-window","-v","-t",c,"-c",a],{cwd:a}),r("tmux",["send-keys","-t",c,"bun run start"],{cwd:a}),r("tmux",["select-pane","-t",`${c}:0.0`],{cwd:a}),g?r("tmux",["switch-client","-t",c],{stdio:"inherit"}):r("tmux",[...x,"attach-session","-t",c],{stdio:"inherit",cwd:a});else if(g)k||r("tmux",["new-session","-d","-s",c,"-c",a,"--",process.execPath,...l],{cwd:a,env:h}),r("tmux",["switch-client","-t",c],{stdio:"inherit"});else{const e=[...x,"new-session","-A","-s",c,"-c",a,"--",process.execPath,...l];r("tmux",e,{stdio:"inherit",cwd:a,env:h})}return{handled:!0}}
1
+ import{feature as e}from"../recovery/bunBundleShim.js";import t from"chalk";import{spawnSync as r}from"child_process";import{copyFile as o,mkdir as n,readdir as a,readFile as s,stat as i,symlink as c,utimes as l}from"fs/promises";import d from"ignore";import{basename as w,dirname as u,join as m}from"path";import{saveCurrentProjectConfig as h}from"./config.js";import{getCwd as f}from"./cwd.js";import{logForDebugging as k}from"./debug.js";import{errorMessage as p,getErrnoCode as g}from"./errors.js";import{execFileNoThrow as $,execFileNoThrowWithCwd as x}from"./execFileNoThrow.js";import{parseGitConfigValue as v}from"./git/gitConfigParser.js";import{getCommonDir as C,readWorktreeHeadSha as y,resolveGitDir as b,resolveRef as W}from"./git/gitFilesystem.js";import{findCanonicalGitRoot as P,findGitRoot as S,getBranch as E,getDefaultBranch as T,gitExe as I}from"./git.js";import{executeWorktreeCreateHook as F,executeWorktreeRemoveHook as A,hasWorktreeCreateHook as _}from"./hooks.js";import{containsPathTraversal as D}from"./path.js";import{getPlatform as B}from"./platform.js";import{getInitialSettings as N,getRelativeSettingsFilePathForSource as R}from"./settings/settings.js";import{sleep as j}from"./sleep.js";import{isInITerm2 as M}from"./swarm/backends/detection.js";const O=/^[a-zA-Z0-9._-]+$/;export function validateWorktreeSlug(e){if(e.length>64)throw new Error(`Invalid worktree name: must be 64 characters or fewer (got ${e.length})`);for(const t of e.split("/")){if("."===t||".."===t)throw new Error(`Invalid worktree name "${e}": must not contain "." or ".." path segments`);if(!O.test(t))throw new Error(`Invalid worktree name "${e}": each "/"-separated segment must be non-empty and contain only letters, digits, dots, underscores, and dashes`)}}let U=null;export function getCurrentWorktreeSession(){return U}export function restoreWorktreeSession(e){U=e}export function generateTmuxSessionName(e,t){return`${w(e)}_${t}`.replace(/[/.]/g,"_")}const H={GIT_TERMINAL_PROMPT:"0",GIT_ASKPASS:""};function worktreesDir(e){return m(e,".contextcli","worktrees")}function flattenSlug(e){return e.replaceAll("/","+")}export function worktreeBranchName(e){return`worktree-${flattenSlug(e)}`}function worktreePathFor(e,t){return m(worktreesDir(e),flattenSlug(t))}async function getOrCreateWorktree(e,t,r){const o=worktreePathFor(e,t),a=worktreeBranchName(t),s=await y(o);if(s)return{worktreePath:o,worktreeBranch:a,headCommit:s,existed:!0};await n(worktreesDir(e),{recursive:!0});const i={...process.env,...H};let c,l=null;if(r?.prNumber){const{code:t,stderr:o}=await x(I(),["fetch","origin",`pull/${r.prNumber}/head`],{cwd:e,stdin:"ignore",env:i});if(0!==t)throw new Error(`Failed to fetch PR #${r.prNumber}: ${o.trim()||'PR may not exist or the repository may not have a remote named "origin"'}`);c="FETCH_HEAD"}else{const[t,r]=await Promise.all([T(),b(e)]),o=`origin/${t}`,n=r?await W(r,`refs/remotes/origin/${t}`):null;if(n)c=o,l=n;else{const{code:r}=await x(I(),["fetch","origin",t],{cwd:e,stdin:"ignore",env:i});c=0===r?o:"HEAD"}}if(!l){const{stdout:t,code:r}=await x(I(),["rev-parse",c],{cwd:e});if(0!==r)throw new Error(`Failed to resolve base branch "${c}": git rev-parse failed`);l=t.trim()}const d=N().worktree?.sparsePaths,w=["worktree","add"];d?.length&&w.push("--no-checkout"),w.push("-B",a,o,c);const{code:u,stderr:m}=await x(I(),w,{cwd:e});if(0!==u)throw new Error(`Failed to create worktree: ${m}`);if(d?.length){const tearDown=async t=>{throw await x(I(),["worktree","remove","--force",o],{cwd:e}),new Error(t)},{code:t,stderr:r}=await x(I(),["sparse-checkout","set","--cone","--",...d],{cwd:o});0!==t&&await tearDown(`Failed to configure sparse-checkout: ${r}`);const{code:n,stderr:a}=await x(I(),["checkout","HEAD"],{cwd:o});0!==n&&await tearDown(`Failed to checkout sparse worktree: ${a}`)}return{worktreePath:o,worktreeBranch:a,headCommit:l,baseBranch:c,existed:!1}}export async function copyWorktreeIncludeFiles(e,t){let r;try{r=await s(m(e,".worktreeinclude"),"utf-8")}catch{return[]}const a=r.split(/\r?\n/).map(e=>e.trim()).filter(e=>e.length>0&&!e.startsWith("#"));if(0===a.length)return[];const i=await x(I(),["ls-files","--others","--ignored","--exclude-standard","--directory"],{cwd:e});if(0!==i.code||!i.stdout.trim())return[];const c=i.stdout.trim().split("\n").filter(Boolean),l=d().add(r),w=c.filter(e=>e.endsWith("/")),h=c.filter(e=>!e.endsWith("/")&&l.ignores(e)),f=w.filter(e=>!!a.some(t=>{const r=t.startsWith("/")?t.slice(1):t;if(r.startsWith(e))return!0;const o=r.search(/[*?[]/);if(o>0){const t=r.slice(0,o);if(e.startsWith(t))return!0}return!1})||!!l.ignores(e.slice(0,-1)));if(f.length>0){const t=await x(I(),["ls-files","--others","--ignored","--exclude-standard","--",...f],{cwd:e});if(0===t.code&&t.stdout.trim())for(const e of t.stdout.trim().split("\n").filter(Boolean))l.ignores(e)&&h.push(e)}const p=[];for(const r of h){const a=m(e,r),s=m(t,r);try{await n(u(s),{recursive:!0}),await o(a,s),p.push(r)}catch(e){k(`Failed to copy ${r} to worktree: ${e.message}`,{level:"warn"})}}return p.length>0&&k(`Copied ${p.length} files from .worktreeinclude: ${p.join(", ")}`),p}async function performPostCreationSetup(t,r){const a=R("localSettings"),s=m(t,a);try{const e=m(r,a);await async function(e){await n(e,{recursive:!0})}(u(e)),await o(s,e),k(`Copied settings.local.json to worktree: ${e}`)}catch(e){"ENOENT"!==g(e)&&k(`Failed to copy settings.local.json: ${e.message}`,{level:"warn"})}const l=m(t,".husky"),d=m(t,".git","hooks");let w=null;for(const e of[l,d])try{if((await i(e)).isDirectory()){w=e;break}}catch{}if(w){const e=await b(t),o=e?await C(e)??e:null;if((o?await v(o,"core",null,"hooksPath"):null)!==w){const{code:e,stderr:t}=await x(I(),["config","core.hooksPath",w],{cwd:r});0===e?k(`Configured worktree to use hooks from main repository: ${w}`):k(`Failed to configure hooks path: ${t}`,{level:"error"})}}const h=N(),f=h.worktree?.symlinkDirectories??[];if(f.length>0&&await async function(e,t,r){for(const o of r){if(D(o)){k(`Skipping symlink for "${o}": path traversal detected`,{level:"warn"});continue}const r=m(e,o),n=m(t,o);try{await c(r,n,"dir"),k(`Symlinked ${o} from main repository to worktree to avoid disk bloat`)}catch(e){const t=g(e);"ENOENT"!==t&&"EEXIST"!==t&&k(`Failed to symlink ${o} (${t??"unknown"}): ${p(e)}`,{level:"warn"})}}}(t,r,f),await copyWorktreeIncludeFiles(t,r),e("COMMIT_ATTRIBUTION")){const e=w===l?m(r,".husky"):void 0;import("./postCommitAttribution.js").then(t=>t.installPrepareCommitMsgHook(r,e).catch(e=>{k(`Failed to install attribution hook in worktree: ${e}`)})).catch(e=>{k(`Failed to load postCommitAttribution module: ${e}`)})}}export function parsePRReference(e){const t=e.match(/^https?:\/\/[^/]+\/[^/]+\/[^/]+\/pull\/(\d+)\/?(?:[?#].*)?$/i);if(t?.[1])return parseInt(t[1],10);const r=e.match(/^#(\d+)$/);return r?.[1]?parseInt(r[1],10):null}export async function isTmuxAvailable(){const{code:e}=await $("tmux",["-V"]);return 0===e}export function getTmuxInstallInstructions(){switch(B()){case"macos":return"Install tmux with: brew install tmux";case"linux":case"wsl":return"Install tmux with: sudo apt install tmux (Debian/Ubuntu) or sudo dnf install tmux (Fedora/RHEL)";case"windows":return"tmux is not natively available on Windows. Consider using WSL or Cygwin.";default:return"Install tmux using your system package manager."}}export async function createTmuxSessionForWorktree(e,t){const{code:r,stderr:o}=await $("tmux",["new-session","-d","-s",e,"-c",t]);return 0!==r?{created:!1,error:o}:{created:!0}}export async function killTmuxSession(e){const{code:t}=await $("tmux",["kill-session","-t",e]);return 0===t}export async function createWorktreeForSession(e,t,r,o){validateWorktreeSlug(t);const n=f();if(_()){const o=await F(t);k(`Created hook-based worktree at: ${o.worktreePath}`),U={originalCwd:n,worktreePath:o.worktreePath,worktreeName:t,sessionId:e,tmuxSessionName:r,hookBased:!0}}else{const a=S(f());if(!a)throw new Error("Cannot create a worktree: not in a git repository and no WorktreeCreate hooks are configured. Configure WorktreeCreate/WorktreeRemove hooks in settings.json to use worktree isolation with other VCS systems.");const s=await E(),i=Date.now(),{worktreePath:c,worktreeBranch:l,headCommit:d,existed:w}=await getOrCreateWorktree(a,t,o);let u;w?k(`Resuming existing worktree at: ${c}`):(k(`Created worktree at: ${c} on branch: ${l}`),await performPostCreationSetup(a,c),u=Date.now()-i),U={originalCwd:n,worktreePath:c,worktreeName:t,worktreeBranch:l,originalBranch:s,originalHeadCommit:d,sessionId:e,tmuxSessionName:r,creationDurationMs:u,usedSparsePaths:(N().worktree?.sparsePaths?.length??0)>0}}return h(e=>({...e,activeWorktreeSession:U??void 0})),U}export async function keepWorktree(){if(U)try{const{worktreePath:e,originalCwd:t,worktreeBranch:r}=U;process.chdir(t),U=null,h(e=>({...e,activeWorktreeSession:void 0})),k(`Linked worktree preserved at: ${e}${r?` on branch: ${r}`:""}`),k(`You can continue working there by running: cd ${e}`)}catch(e){k(`Error keeping worktree: ${e}`,{level:"error"})}}export async function cleanupWorktree(){if(U)try{const{worktreePath:e,originalCwd:t,worktreeBranch:r,hookBased:o}=U;if(process.chdir(t),o){await A(e)?k(`Removed hook-based worktree at: ${e}`):k(`No WorktreeRemove hook configured, hook-based worktree left at: ${e}`,{level:"warn"})}else{const{code:r,stderr:o}=await x(I(),["worktree","remove","--force",e],{cwd:t});0!==r?k(`Failed to remove linked worktree: ${o}`,{level:"error"}):k(`Removed linked worktree at: ${e}`)}if(U=null,h(e=>({...e,activeWorktreeSession:void 0})),!o&&r){await j(100);const{code:e,stderr:o}=await x(I(),["branch","-D",r],{cwd:t});0!==e?k(`Could not delete worktree branch: ${o}`,{level:"error"}):k(`Deleted worktree branch: ${r}`)}k("Linked worktree cleaned up completely")}catch(e){k(`Error cleaning up worktree: ${e}`,{level:"error"})}}export async function createAgentWorktree(e){if(validateWorktreeSlug(e),_()){const t=await F(e);return k(`Created hook-based agent worktree at: ${t.worktreePath}`),{worktreePath:t.worktreePath,hookBased:!0}}const t=P(f());if(!t)throw new Error("Cannot create agent worktree: not in a git repository and no WorktreeCreate hooks are configured. Configure WorktreeCreate/WorktreeRemove hooks in settings.json to use worktree isolation with other VCS systems.");const{worktreePath:r,worktreeBranch:o,headCommit:n,existed:a}=await getOrCreateWorktree(t,e);if(a){const e=new Date;await l(r,e,e),k(`Resuming existing agent worktree at: ${r}`)}else k(`Created agent worktree at: ${r} on branch: ${o}`),await performPostCreationSetup(t,r);return{worktreePath:r,worktreeBranch:o,headCommit:n,gitRoot:t}}export async function removeAgentWorktree(e,t,r,o){if(o){const t=await A(e);return t?k(`Removed hook-based agent worktree at: ${e}`):k(`No WorktreeRemove hook configured, hook-based agent worktree left at: ${e}`,{level:"warn"}),t}if(!r)return k("Cannot remove agent worktree: no git root provided",{level:"error"}),!1;const{code:n,stderr:a}=await x(I(),["worktree","remove","--force",e],{cwd:r});if(0!==n)return k(`Failed to remove agent worktree: ${a}`,{level:"error"}),!1;if(k(`Removed agent worktree at: ${e}`),!t)return!0;const{code:s,stderr:i}=await x(I(),["branch","-D",t],{cwd:r});return 0!==s&&k(`Could not delete agent worktree branch: ${i}`,{level:"error"}),!0}const L=[/^agent-a[0-9a-f]{7}$/,/^wf_[0-9a-f]{8}-[0-9a-f]{3}-\d+$/,/^wf-\d+$/,/^bridge-[A-Za-z0-9_]+(-[A-Za-z0-9_]+)*$/,/^job-[a-zA-Z0-9._-]{1,55}-[0-9a-f]{8}$/];export async function cleanupStaleAgentWorktrees(e){const t=P(f());if(!t)return 0;const r=worktreesDir(t);let o;try{o=await a(r)}catch{return 0}const n=e.getTime(),s=U?.worktreePath;let c=0;for(const e of o){if(!L.some(t=>t.test(e)))continue;const o=m(r,e);if(s===o)continue;let a;try{a=(await i(o)).mtimeMs}catch{continue}if(a>=n)continue;const[l,d]=await Promise.all([x(I(),["--no-optional-locks","status","--porcelain","-uno"],{cwd:o}),x(I(),["rev-list","--max-count=1","HEAD","--not","--remotes"],{cwd:o})]);0!==l.code||l.stdout.trim().length>0||(0!==d.code||d.stdout.trim().length>0||await removeAgentWorktree(o,worktreeBranchName(e),t)&&c++)}return c>0&&(await x(I(),["worktree","prune"],{cwd:t}),k(`cleanupStaleAgentWorktrees: removed ${c} stale worktree(s)`)),c}export async function hasWorktreeChanges(e,t){const{code:r,stdout:o}=await x(I(),["status","--porcelain"],{cwd:e});if(0!==r)return!0;if(o.trim().length>0)return!0;const{code:n,stdout:a}=await x(I(),["rev-list","--count",`${t}..HEAD`],{cwd:e});return 0!==n||parseInt(a.trim(),10)>0}export async function execIntoTmuxWorktree(e){if("win32"===process.platform)return{handled:!1,error:"Error: --tmux is not supported on Windows"};if(0!==r("tmux",["-V"],{encoding:"utf-8"}).status){return{handled:!1,error:`Error: tmux is not installed. ${"darwin"===process.platform?"Install tmux with: brew install tmux":"Install tmux with: sudo apt install tmux"}`}}let o,n=!1;for(let t=0;t<e.length;t++){const r=e[t];if(r)if("-w"===r||"--worktree"===r){const r=e[t+1];r&&!r.startsWith("-")&&(o=r)}else r.startsWith("--worktree=")?o=r.slice(11):"--tmux=classic"===r&&(n=!0)}let a,s,i=null;if(o&&(i=parsePRReference(o),null!==i&&(o=`pr-${i}`)),!o){const e=["swift","bright","calm","keen","bold"],t=["fox","owl","elm","oak","ray"];o=`${e[Math.floor(Math.random()*e.length)]}-${t[Math.floor(Math.random()*t.length)]}-${Math.random().toString(36).slice(2,6)}`}try{validateWorktreeSlug(o)}catch(e){return{handled:!1,error:`Error: ${e.message}`}}if(_()){try{a=(await F(o)).worktreePath}catch(e){return{handled:!1,error:`Error: ${p(e)}`}}s=w(P(f())??f()),console.log(`Using worktree via hook: ${a}`)}else{const e=P(f());if(!e)return{handled:!1,error:"Error: --worktree requires a git repository"};s=w(e),a=worktreePathFor(e,o);try{const t=await getOrCreateWorktree(e,o,null!==i?{prNumber:i}:void 0);t.existed||(console.log(`Created worktree: ${a} (based on ${t.baseBranch})`),await performPostCreationSetup(e,a))}catch(e){return{handled:!1,error:`Error: ${p(e)}`}}}const c=`${s}_${worktreeBranchName(o)}`.replace(/[/.]/g,"_"),l=[];for(let t=0;t<e.length;t++){const r=e[t];if(r&&("--tmux"!==r&&"--tmux=classic"!==r)){if("-w"===r||"--worktree"===r){const r=e[t+1];r&&!r.startsWith("-")&&t++;continue}r.startsWith("--worktree=")||l.push(r)}}let d="C-b";const u=r("tmux",["show-options","-g","prefix"],{encoding:"utf-8"});if(0===u.status&&u.stdout){const e=u.stdout.match(/prefix\s+(\S+)/);e?.[1]&&(d=e[1])}const m=["C-b","C-c","C-d","C-t","C-o","C-r","C-s","C-g","C-e"].includes(d),h={...process.env,CLAUDE_CODE_TMUX_SESSION:c,CLAUDE_CODE_TMUX_PREFIX:d,CLAUDE_CODE_TMUX_PREFIX_CONFLICTS:m?"1":""},k=0===r("tmux",["has-session","-t",c],{encoding:"utf-8"}).status,g=Boolean(process.env.TMUX),$=M()&&!n&&!g,x=$?["-CC"]:[];if($&&!k){const e=t.yellow;console.log(`\n${e("╭─ iTerm2 Tip ────────────────────────────────────────────────────────╮")}\n${e("│")} To open as a tab instead of a new window: ${e("│")}\n${e("│")} iTerm2 > Settings > General > tmux > "Tabs in attaching window" ${e("│")}\n${e("╰─────────────────────────────────────────────────────────────────────╯")}\n`)}if("ant"===process.env.USER_TYPE&&"claude-cli-internal"===s&&!k)r("tmux",["new-session","-d","-s",c,"-c",a,"--",process.execPath,...l],{cwd:a,env:h}),r("tmux",["split-window","-h","-t",c,"-c",a],{cwd:a}),r("tmux",["send-keys","-t",c,"bun run watch","Enter"],{cwd:a}),r("tmux",["split-window","-v","-t",c,"-c",a],{cwd:a}),r("tmux",["send-keys","-t",c,"bun run start"],{cwd:a}),r("tmux",["select-pane","-t",`${c}:0.0`],{cwd:a}),g?r("tmux",["switch-client","-t",c],{stdio:"inherit"}):r("tmux",[...x,"attach-session","-t",c],{stdio:"inherit",cwd:a});else if(g)k||r("tmux",["new-session","-d","-s",c,"-c",a,"--",process.execPath,...l],{cwd:a,env:h}),r("tmux",["switch-client","-t",c],{stdio:"inherit"});else{const e=[...x,"new-session","-A","-s",c,"-c",a,"--",process.execPath,...l];r("tmux",e,{stdio:"inherit",cwd:a,env:h})}return{handled:!0}}
@@ -1 +1 @@
1
- import{homedir as e}from"node:os";import{basename as r,join as t,resolve as n}from"node:path";import{getGlobalConfig as a,saveGlobalConfig as o}from"../utils/config.js";import{getSecureStorage as i}from"../utils/secureStorage/index.js";function writeSecureConfig(e){const r=i(),t=r.read()??{};if(!e){const{whatsappBot:e,...n}=t;return void r.update(n)}r.update({...t,whatsappBot:e})}function clearLegacyConfig(){o(e=>{const{whatsappBot:r,...t}=e;return t})}export function getWhatsAppConfig(){const e=i().read()?.whatsappBot;if(e)return e;const r=a().whatsappBot;return r&&(writeSecureConfig(r),clearLegacyConfig()),r}export function getWhatsAppAuthDir(){const r=getWhatsAppConfig()?.authDir?.trim();if(r)return r;const n=process.env.CONTEXT_CONFIG_DIR?.trim()||process.env.CLAUDE_CONFIG_DIR?.trim()||t(e(),".context");return t(n,"whatsapp-auth")}export function getAllowedNumbers(){return getWhatsAppConfig()?.allowedNumbers??[]}export function getPrimaryRecipient(){return getWhatsAppConfig()?.primaryRecipient}export function listWhatsAppWorkspaces(){const e=getWhatsAppConfig();return e?.workspaces??[]}export function getActiveWorkspaceAlias(){return getWhatsAppConfig()?.activeWorkspaceAlias}export function getActiveWorkspaceAliasForNumber(e){const r=normalizeE164(e),t=getWhatsAppConfig();return t?.activeWorkspaceByNumber?.[r]??t?.activeWorkspaceAlias}export function getActiveWorkspace(){const e=getWhatsAppConfig(),r=e?.activeWorkspaceAlias;if(r)return(e?.workspaces??[]).find(e=>e.alias===r)}export function getActiveWorkspaceForNumber(e){const r=getActiveWorkspaceAliasForNumber(e);if(r)return listWhatsAppWorkspaces().find(e=>e.alias===r)}export function setActiveWorkspaceAlias(e){const r=normalizeAlias(e),t=getWhatsAppConfig(),n=t?.workspaces??[];return!!n.some(e=>e.alias===r)&&(writeSecureConfig({...t??{},running:t?.running??!1,allowedNumbers:t?.allowedNumbers??[],activeWorkspaceAlias:r,workspaces:n}),clearLegacyConfig(),!0)}export function setActiveWorkspaceAliasForNumber(e,r){const t=normalizeAlias(r),n=normalizeE164(e),a=getWhatsAppConfig(),o=a?.workspaces??[];if(!o.some(e=>e.alias===t)||!n)return!1;const i={...a?.activeWorkspaceByNumber??{}};return i[n]=t,writeSecureConfig({...a??{},running:a?.running??!1,allowedNumbers:a?.allowedNumbers??[],activeWorkspaceByNumber:i,activeWorkspaceAlias:a?.activeWorkspaceAlias??t,workspaces:o}),clearLegacyConfig(),!0}export function ensureCurrentWorkspaceRegistered(){const e=normalizeWorkspacePath(process.cwd()),r=(new Date).toISOString(),t=getWhatsAppConfig(),n=[...t?.workspaces??[]],a=n.findIndex(r=>normalizeWorkspacePath(r.path)===e);if(a>=0){const o={...n[a],lastSeenAt:r,path:e};return n[a]=o,writeSecureConfig({...t??{},running:t?.running??!1,allowedNumbers:t?.allowedNumbers??[],workspaces:n,activeWorkspaceAlias:t?.activeWorkspaceAlias??o.alias}),clearLegacyConfig(),o}const o=makeUniqueAlias(n,deriveAliasFromPath(e)),i={alias:o,path:e,createdAt:r,lastSeenAt:r};return n.push(i),writeSecureConfig({...t??{},running:t?.running??!1,allowedNumbers:t?.allowedNumbers??[],workspaces:n,activeWorkspaceAlias:o}),clearLegacyConfig(),i}export function ensureWorkspaceRegisteredByPath(e){const r=normalizeWorkspacePath(e),t=(new Date).toISOString(),n=getWhatsAppConfig(),a=[...n?.workspaces??[]],o=a.findIndex(e=>normalizeWorkspacePath(e.path)===r);if(o>=0){const e={...a[o],path:r,lastSeenAt:t};return a[o]=e,writeSecureConfig({...n??{},running:n?.running??!1,allowedNumbers:n?.allowedNumbers??[],workspaces:a,activeWorkspaceAlias:n?.activeWorkspaceAlias??e.alias}),clearLegacyConfig(),e}const i={alias:makeUniqueAlias(a,deriveAliasFromPath(r)),path:r,createdAt:t,lastSeenAt:t};return a.push(i),writeSecureConfig({...n??{},running:n?.running??!1,allowedNumbers:n?.allowedNumbers??[],workspaces:a,activeWorkspaceAlias:n?.activeWorkspaceAlias??i.alias}),clearLegacyConfig(),i}export function setWhatsAppRunning(e){const r=getWhatsAppConfig();writeSecureConfig({...r??{},running:e,allowedNumbers:r?.allowedNumbers??[]}),clearLegacyConfig()}export function setPrimaryRecipient(e){const r=normalizeE164(e),t=getWhatsAppConfig(),n=t?.allowedNumbers??[],a=n.includes(r)?n:[...n,r];writeSecureConfig({...t??{},running:t?.running??!1,primaryRecipient:r,allowedNumbers:a}),clearLegacyConfig()}export function addAllowedNumber(e){const r=normalizeE164(e),t=getWhatsAppConfig(),n=t?.allowedNumbers??[];n.includes(r)||(writeSecureConfig({...t??{},running:t?.running??!1,allowedNumbers:[...n,r]}),clearLegacyConfig())}export function removeAllowedNumber(e){const r=normalizeE164(e),t=getWhatsAppConfig(),n=t?.allowedNumbers??[];writeSecureConfig({...t??{},running:t?.running??!1,allowedNumbers:n.filter(e=>e!==r)}),clearLegacyConfig()}export function setSelfIdentity(e,r){const t=getWhatsAppConfig();writeSecureConfig({...t??{},running:t?.running??!1,selfJid:e,selfE164:r,allowedNumbers:t?.allowedNumbers??[]}),clearLegacyConfig()}export function clearWhatsAppConfig(){writeSecureConfig(void 0),clearLegacyConfig()}export function clearLinkedIdentity(){const e=getWhatsAppConfig();writeSecureConfig({...e??{},selfJid:void 0,selfE164:void 0,running:!1,allowedNumbers:e?.allowedNumbers??[]}),clearLegacyConfig()}export function normalizeE164(e){const r=e.trim().replace(/[\s()\-]/g,"");if(!r)return"";if(r.startsWith("+"))return`+${r.slice(1).replace(/\D/g,"")}`;const t=r.replace(/\D/g,"");return t?`+${t}`:""}export function e164ToJid(e){return`${normalizeE164(e).replace(/^\+/,"")}@s.whatsapp.net`}export function jidToE164(e){const r=(e.split(":")[0]??e).split("@")[0]??"";return r&&/^\d+$/.test(r)?`+${r}`:null}function makeUniqueAlias(e,r){const t=new Set(e.map(e=>e.alias));if(!t.has(r))return r;let n=2;for(;t.has(`${r}-${n}`);)n+=1;return`${r}-${n}`}function deriveAliasFromPath(e){return normalizeAlias(r(e)||"workspace")||"workspace"}function normalizeAlias(e){return e.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)||"workspace"}function normalizeWorkspacePath(e){const r=n(e);return"win32"===process.platform?r.toLowerCase():r}
1
+ import{homedir as e}from"node:os";import{basename as r,join as t,resolve as n}from"node:path";import{getGlobalConfig as a,saveGlobalConfig as o}from"../utils/config.js";import{getSecureStorage as i}from"../utils/secureStorage/index.js";function writeSecureConfig(e){const r=i(),t=r.read()??{};if(!e){const{whatsappBot:e,...n}=t;return void r.update(n)}r.update({...t,whatsappBot:e})}function clearLegacyConfig(){o(e=>{const{whatsappBot:r,...t}=e;return t})}export function getWhatsAppConfig(){const e=i().read()?.whatsappBot;if(e)return e;const r=a().whatsappBot;return r&&(writeSecureConfig(r),clearLegacyConfig()),r}export function getWhatsAppAuthDir(){const r=getWhatsAppConfig()?.authDir?.trim();if(r)return r;const n=process.env.CONTEXT_CONFIG_DIR?.trim()||process.env.CLAUDE_CONFIG_DIR?.trim()||t(e(),".contextcli");return t(n,"whatsapp-auth")}export function getAllowedNumbers(){return getWhatsAppConfig()?.allowedNumbers??[]}export function getPrimaryRecipient(){return getWhatsAppConfig()?.primaryRecipient}export function listWhatsAppWorkspaces(){const e=getWhatsAppConfig();return e?.workspaces??[]}export function getActiveWorkspaceAlias(){return getWhatsAppConfig()?.activeWorkspaceAlias}export function getActiveWorkspaceAliasForNumber(e){const r=normalizeE164(e),t=getWhatsAppConfig();return t?.activeWorkspaceByNumber?.[r]??t?.activeWorkspaceAlias}export function getActiveWorkspace(){const e=getWhatsAppConfig(),r=e?.activeWorkspaceAlias;if(r)return(e?.workspaces??[]).find(e=>e.alias===r)}export function getActiveWorkspaceForNumber(e){const r=getActiveWorkspaceAliasForNumber(e);if(r)return listWhatsAppWorkspaces().find(e=>e.alias===r)}export function setActiveWorkspaceAlias(e){const r=normalizeAlias(e),t=getWhatsAppConfig(),n=t?.workspaces??[];return!!n.some(e=>e.alias===r)&&(writeSecureConfig({...t??{},running:t?.running??!1,allowedNumbers:t?.allowedNumbers??[],activeWorkspaceAlias:r,workspaces:n}),clearLegacyConfig(),!0)}export function setActiveWorkspaceAliasForNumber(e,r){const t=normalizeAlias(r),n=normalizeE164(e),a=getWhatsAppConfig(),o=a?.workspaces??[];if(!o.some(e=>e.alias===t)||!n)return!1;const i={...a?.activeWorkspaceByNumber??{}};return i[n]=t,writeSecureConfig({...a??{},running:a?.running??!1,allowedNumbers:a?.allowedNumbers??[],activeWorkspaceByNumber:i,activeWorkspaceAlias:a?.activeWorkspaceAlias??t,workspaces:o}),clearLegacyConfig(),!0}export function ensureCurrentWorkspaceRegistered(){const e=normalizeWorkspacePath(process.cwd()),r=(new Date).toISOString(),t=getWhatsAppConfig(),n=[...t?.workspaces??[]],a=n.findIndex(r=>normalizeWorkspacePath(r.path)===e);if(a>=0){const o={...n[a],lastSeenAt:r,path:e};return n[a]=o,writeSecureConfig({...t??{},running:t?.running??!1,allowedNumbers:t?.allowedNumbers??[],workspaces:n,activeWorkspaceAlias:t?.activeWorkspaceAlias??o.alias}),clearLegacyConfig(),o}const o=makeUniqueAlias(n,deriveAliasFromPath(e)),i={alias:o,path:e,createdAt:r,lastSeenAt:r};return n.push(i),writeSecureConfig({...t??{},running:t?.running??!1,allowedNumbers:t?.allowedNumbers??[],workspaces:n,activeWorkspaceAlias:o}),clearLegacyConfig(),i}export function ensureWorkspaceRegisteredByPath(e){const r=normalizeWorkspacePath(e),t=(new Date).toISOString(),n=getWhatsAppConfig(),a=[...n?.workspaces??[]],o=a.findIndex(e=>normalizeWorkspacePath(e.path)===r);if(o>=0){const e={...a[o],path:r,lastSeenAt:t};return a[o]=e,writeSecureConfig({...n??{},running:n?.running??!1,allowedNumbers:n?.allowedNumbers??[],workspaces:a,activeWorkspaceAlias:n?.activeWorkspaceAlias??e.alias}),clearLegacyConfig(),e}const i={alias:makeUniqueAlias(a,deriveAliasFromPath(r)),path:r,createdAt:t,lastSeenAt:t};return a.push(i),writeSecureConfig({...n??{},running:n?.running??!1,allowedNumbers:n?.allowedNumbers??[],workspaces:a,activeWorkspaceAlias:n?.activeWorkspaceAlias??i.alias}),clearLegacyConfig(),i}export function setWhatsAppRunning(e){const r=getWhatsAppConfig();writeSecureConfig({...r??{},running:e,allowedNumbers:r?.allowedNumbers??[]}),clearLegacyConfig()}export function setPrimaryRecipient(e){const r=normalizeE164(e),t=getWhatsAppConfig(),n=t?.allowedNumbers??[],a=n.includes(r)?n:[...n,r];writeSecureConfig({...t??{},running:t?.running??!1,primaryRecipient:r,allowedNumbers:a}),clearLegacyConfig()}export function addAllowedNumber(e){const r=normalizeE164(e),t=getWhatsAppConfig(),n=t?.allowedNumbers??[];n.includes(r)||(writeSecureConfig({...t??{},running:t?.running??!1,allowedNumbers:[...n,r]}),clearLegacyConfig())}export function removeAllowedNumber(e){const r=normalizeE164(e),t=getWhatsAppConfig(),n=t?.allowedNumbers??[];writeSecureConfig({...t??{},running:t?.running??!1,allowedNumbers:n.filter(e=>e!==r)}),clearLegacyConfig()}export function setSelfIdentity(e,r){const t=getWhatsAppConfig();writeSecureConfig({...t??{},running:t?.running??!1,selfJid:e,selfE164:r,allowedNumbers:t?.allowedNumbers??[]}),clearLegacyConfig()}export function clearWhatsAppConfig(){writeSecureConfig(void 0),clearLegacyConfig()}export function clearLinkedIdentity(){const e=getWhatsAppConfig();writeSecureConfig({...e??{},selfJid:void 0,selfE164:void 0,running:!1,allowedNumbers:e?.allowedNumbers??[]}),clearLegacyConfig()}export function normalizeE164(e){const r=e.trim().replace(/[\s()\-]/g,"");if(!r)return"";if(r.startsWith("+"))return`+${r.slice(1).replace(/\D/g,"")}`;const t=r.replace(/\D/g,"");return t?`+${t}`:""}export function e164ToJid(e){return`${normalizeE164(e).replace(/^\+/,"")}@s.whatsapp.net`}export function jidToE164(e){const r=(e.split(":")[0]??e).split("@")[0]??"";return r&&/^\d+$/.test(r)?`+${r}`:null}function makeUniqueAlias(e,r){const t=new Set(e.map(e=>e.alias));if(!t.has(r))return r;let n=2;for(;t.has(`${r}-${n}`);)n+=1;return`${r}-${n}`}function deriveAliasFromPath(e){return normalizeAlias(r(e)||"workspace")||"workspace"}function normalizeAlias(e){return e.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)||"workspace"}function normalizeWorkspacePath(e){const r=n(e);return"win32"===process.platform?r.toLowerCase():r}