开源 企业版 高校版 私有云 模力方舟 AI 队友
代码拉取完成,页面将自动刷新
加入 Gitee
与超过 1400万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
已有帐号? 立即登录
文件
main
分支 (5)
标签 (3)
main
feature/v6
test/test-most-core-func
version/i18n/zh-cn
feature/prod
v3
v2
v1
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
项目仓库所选许可证以仓库主分支所使用许可证为准
main
分支 (5)
标签 (3)
main
feature/v6
test/test-most-core-func
version/i18n/zh-cn
feature/prod
v3
v2
v1
克隆/下载
克隆/下载
提示
下载代码请复制以下命令到终端执行
为确保你提交的代码身份被 Gitee 正确识别,请执行以下命令完成配置
初次使用 SSH 协议进行代码克隆、推送等操作时,需按下述提示完成 SSH 配置
1 生成 RSA 密钥
2 获取 RSA 公钥内容,并配置到 SSH公钥
在 Gitee 上使用 SVN,请访问 使用指南
使用 HTTPS 协议时,命令行会出现如下账号密码验证步骤。基于安全考虑,Gitee 建议 配置并使用私人令牌 替代登录密码进行克隆、推送等操作
Username for 'https://gitee.com': userName
Password for 'https://userName@gitee.com': # 私人令牌
main
分支 (5)
标签 (3)
main
feature/v6
test/test-most-core-func
version/i18n/zh-cn
feature/prod
v3
v2
v1
claude-code
/
src
/
utils
/
plugins
/
pluginDirectories.ts
claude-code
/
src
/
utils
/
plugins
/
pluginDirectories.ts
pluginDirectories.ts 6.51 KB
一键复制 编辑 原始数据 按行查看 历史
claude-code-best 提交于 2026年03月31日 19:22 +08:00 . feat: build
/**
* Centralized plugin directory configuration.
*
* This module provides the single source of truth for the plugins directory path.
* It supports switching between 'plugins' and 'cowork_plugins' directories via:
* - CLI flag: --cowork
* - Environment variable: CLAUDE_CODE_USE_COWORK_PLUGINS
*
* The base directory can be overridden via CLAUDE_CODE_PLUGIN_CACHE_DIR.
*/
import { mkdirSync } from 'fs'
import { readdir, rm, stat } from 'fs/promises'
import { delimiter, join } from 'path'
import { getUseCoworkPlugins } from '../../bootstrap/state.js'
import { logForDebugging } from '../debug.js'
import { getClaudeConfigHomeDir, isEnvTruthy } from '../envUtils.js'
import { errorMessage, isFsInaccessible } from '../errors.js'
import { formatFileSize } from '../format.js'
import { expandTilde } from '../permissions/pathValidation.js'
const PLUGINS_DIR = 'plugins'
const COWORK_PLUGINS_DIR = 'cowork_plugins'
/**
* Get the plugins directory name based on current mode.
* Uses session state (from --cowork flag) or env var.
*
* Priority:
* 1. Session state (set by CLI flag --cowork)
* 2. Environment variable CLAUDE_CODE_USE_COWORK_PLUGINS
* 3. Default: 'plugins'
*/
function getPluginsDirectoryName(): string {
// Session state takes precedence (set by CLI flag)
if (getUseCoworkPlugins()) {
return COWORK_PLUGINS_DIR
}
// Fall back to env var
if (isEnvTruthy(process.env.CLAUDE_CODE_USE_COWORK_PLUGINS)) {
return COWORK_PLUGINS_DIR
}
return PLUGINS_DIR
}
/**
* Get the full path to the plugins directory.
*
* Priority:
* 1. CLAUDE_CODE_PLUGIN_CACHE_DIR env var (explicit override)
* 2. Default: ~/.claude/plugins or ~/.claude/cowork_plugins
*/
export function getPluginsDirectory(): string {
// expandTilde: when CLAUDE_CODE_PLUGIN_CACHE_DIR is set via settings.json
// `env` (not shell), ~ is not expanded by the shell. Without this, a value
// like "~/.claude/plugins" becomes a literal `~` directory created in the
// cwd of every project (gh-30794 / CC-212).
const envOverride = process.env.CLAUDE_CODE_PLUGIN_CACHE_DIR
if (envOverride) {
return expandTilde(envOverride)
}
return join(getClaudeConfigHomeDir(), getPluginsDirectoryName())
}
/**
* Get the read-only plugin seed directories, if configured.
*
* Customers can pre-bake a populated plugins directory into their container
* image and point CLAUDE_CODE_PLUGIN_SEED_DIR at it. CC will use it as a
* read-only fallback layer under the primary plugins directory — marketplaces
* and plugin caches found in the seed are used in place without re-cloning.
*
* Multiple seed directories can be layered using the platform path delimiter
* (':' on Unix, ';' on Windows), in PATH-like precedence order — the first
* seed that contains a given marketplace or plugin cache wins.
*
* Seed structure mirrors the primary plugins directory:
* $CLAUDE_CODE_PLUGIN_SEED_DIR/
* known_marketplaces.json
* marketplaces/<name>/...
* cache/<marketplace>/<plugin>/<version>/...
*
* @returns Absolute paths to seed dirs in precedence order (empty if unset)
*/
export function getPluginSeedDirs(): string[] {
// Same tilde-expansion rationale as getPluginsDirectory (gh-30794).
const raw = process.env.CLAUDE_CODE_PLUGIN_SEED_DIR
if (!raw) return []
return raw.split(delimiter).filter(Boolean).map(expandTilde)
}
function sanitizePluginId(pluginId: string): string {
// Same character class as the install-cache sanitizer (pluginLoader.ts)
return pluginId.replace(/[^a-zA-Z0-9\-_]/g, '-')
}
/** Pure path — no mkdir. For display (e.g. uninstall dialog). */
export function pluginDataDirPath(pluginId: string): string {
return join(getPluginsDirectory(), 'data', sanitizePluginId(pluginId))
}
/**
* Persistent per-plugin data directory, exposed to plugins as
* ${CLAUDE_PLUGIN_DATA}. Unlike the version-scoped install cache
* (${CLAUDE_PLUGIN_ROOT}, which is orphaned and GC'd on every update),
* this survives plugin updates — only removed on last-scope uninstall.
*
* Creates the directory on call (mkdir). The *lazy* behavior is at the
* substitutePluginVariables call site — the DATA pattern uses function-form
* .replace() so this isn't invoked unless ${CLAUDE_PLUGIN_DATA} is present
* (ROOT also uses function-form, but for $-pattern safety, not laziness).
* Env-var export sites (MCP/LSP server env, hook env) call this eagerly
* since subprocesses may expect the dir to exist before writing to it.
*
* Sync because it's called from substitutePluginVariables (sync, inside
* String.replace) — making this async would cascade through 6 call sites
* and their sync iteration loops. One mkdir in plugin-load path is cheap.
*/
export function getPluginDataDir(pluginId: string): string {
const dir = pluginDataDirPath(pluginId)
mkdirSync(dir, { recursive: true })
return dir
}
/**
* Size of the data dir for the uninstall confirmation prompt. Returns null
* when the dir is absent or empty so callers can skip the prompt entirely.
* Recursive walk — not hot-path (only on uninstall).
*/
export async function getPluginDataDirSize(
pluginId: string,
): Promise<{ bytes: number; human: string } | null> {
const dir = pluginDataDirPath(pluginId)
let bytes = 0
const walk = async (p: string) => {
for (const entry of await readdir(p, { withFileTypes: true })) {
const full = join(p, entry.name)
if (entry.isDirectory()) {
await walk(full)
} else {
// Per-entry catch: a broken symlink makes stat() throw ENOENT.
// Without this, one broken link bubbles to the outer catch →
// returns null → dialog skipped → data silently deleted.
try {
bytes += (await stat(full)).size
} catch {
// Broken symlink / raced delete — skip this entry, keep walking
}
}
}
}
try {
await walk(dir)
} catch (e) {
if (isFsInaccessible(e)) return null
throw e
}
if (bytes === 0) return null
return { bytes, human: formatFileSize(bytes) }
}
/**
* Best-effort cleanup on last-scope uninstall. Failure is logged but does
* not throw — the uninstall itself already succeeded; we don't want a
* cleanup side-effect surfacing as "uninstall failed". Same rationale as
* deletePluginOptions (pluginOptionsStorage.ts).
*/
export async function deletePluginDataDir(pluginId: string): Promise<void> {
const dir = pluginDataDirPath(pluginId)
try {
await rm(dir, { recursive: true, force: true })
} catch (e) {
logForDebugging(
`Failed to delete plugin data dir ${dir}: ${errorMessage(e)}`,
{ level: 'warn' },
)
}
}
Loading...
举报
举报成功
我们将于2个工作日内通过站内信反馈结果给你!
请认真填写举报原因,尽可能描述详细。
请选择举报类型
取消
发送
误判申诉

此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。

如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。

取消
提交

发行版

暂无发行版

贡献者

全部

近期动态

不能加载更多了
编辑仓库简介
简介内容
主页
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/jbcode/claude-code.git
git@gitee.com:jbcode/claude-code.git
jbcode
claude-code
claude-code
main
点此查找更多帮助

搜索帮助

评论
仓库举报
回到顶部
登录提示
该操作需登录 Gitee 帐号,请先登录后再操作。
立即登录
没有帐号,去注册

AltStyle によって変換されたページ (->オリジナル) /