制作可编辑图层海报并导出 PSD 或平面图
30.7/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/19 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add full-aigc-plugins/photocraft-plugin该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · vision
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 16 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 126 处,展示前 109 条)
- 读取本地文件dsh/lib/index.js:151
if (!existsSync(SKILLS_DIR)) return [] - 读取本地文件dsh/lib/index.js:155
entries = await readdir(SKILLS_DIR, { withFileTypes: true }) - 读取本地文件dsh/lib/index.js:163
if (!existsSync(file)) continue - 读取本地文件dsh/lib/index.js:165
const parsed = parseSkill(await readFile(file, 'utf8')) - 读取本地文件dsh/lib/index.js:191
if (!existsSync(COMMANDS_DIR)) return [] - 读取本地文件dsh/lib/index.js:195
entries = await readdir(COMMANDS_DIR, { withFileTypes: true }) - 写入本地文件dsh/lib/index.js:305
mkdir(DATA_DIR, { recursive: true }).catch(() => {}) - 读取本地文件src/cli.ts:33
const state=safePath(options['--state-dir']);if(!existsSync(join(state,'tasks.sqlite')))throw new Error('ledger_missing'); - 读取本地文件src/cli.ts:43
if(action!=='create' && !existsSync(join(state,'tasks.sqlite')))throw new Error('ledger_missing'); - 读取本地文件src/cli.ts:44
let selected:any;if(existsSync(join(state,'tasks.sqlite'))){const prior=new Ledger(state,{readOnly:true});try{selected=runtimeBinding(prior);}finally{ - 读取本地文件src/cli.ts:51
if(existsSync(join(state,'tasks.sqlite'))) { - 发起网络请求src/cli.ts:77
case 'judge':result=new Review(ledger).request(id);break; - 读取本地文件src/protocol/artifact.ts:58
const artifact={protocolVersion:'craft-artifact/v1',assetId,version,sha256:version,bytes:statSync(join(directory,'project.pcraft')).size,mediaType:'ap - 读取本地文件src/protocol/bundle.ts:33
if(existsSync(target))throw new Error('bundle_output_exists'); - 写入本地文件src/protocol/bundle.ts:37
const copy=(name:string,path:string)=>{const output=pathAt(stage,name);mkdirSync(dirname(output),{recursive:true});copyFileSync(safePath(path),output) - 写入本地文件src/protocol/bundle.ts:37
const copy=(name:string,path:string)=>{const output=pathAt(stage,name);mkdirSync(dirname(output),{recursive:true});copyFileSync(safePath(path),output) - 写入本地文件src/protocol/bundle.ts:38
const save=(name:string,value:any)=>{const path=pathAt(stage,name);mkdirSync(dirname(path),{recursive:true});writeFileSync(path,canonical(value),{flag - 写入本地文件src/protocol/bundle.ts:38
const save=(name:string,value:any)=>{const path=pathAt(stage,name);mkdirSync(dirname(path),{recursive:true});writeFileSync(path,canonical(value),{flag - 写入本地文件src/protocol/bundle.ts:54
renameSync(stage,target);return {...result,directory:target}; - 删除本地文件递归src/protocol/bundle.ts:55
}catch(error){rmSync(stage,{recursive:true,force:true});throw error;} - 读取本地文件src/protocol/bundle.ts:61
if(!existsSync(manifestPath)) { - 读取本地文件src/protocol/files.ts:24
export const readJson = (path: string) => strictJson(decodeUtf8(readFileSync(safePath(path)))); - 读取本地文件src/protocol/files.ts:25
export const fileDigest = (path: string) => digest(readFileSync(safePath(path))); - 读取本地文件src/protocol/files.ts:32
if (existsSync(current) || (() => { try { return lstatSync(current).isSymbolicLink(); } catch { return false; } })()) { - 读取本地文件src/protocol/files.ts:32
if (existsSync(current) || (() => { try { return lstatSync(current).isSymbolicLink(); } catch { return false; } })()) { - 读取本地文件src/protocol/files.ts:33
if (lstatSync(current).isSymbolicLink()) throw new Error('symlink_not_allowed'); - 读取本地文件ui/adapter.mjs:16
? JSON.parse(await readFile(process.env.CRAFT_PANEL_METADATA_FILE, 'utf8')) : {}; - 读取本地文件ui/adapter.mjs:19
let config=JSON.parse(await readFile(join(here,'editor/assets.json'),'utf8')); - 读取本地文件ui/adapter.mjs:26
if(runtime.path!==editorRoot){if(connecting)await connecting;if(client)await client.close();client=null;catalog=null;editorRoot=runtime.path;config=JS - 读取本地文件ui/adapter.mjs:47
const selected=await realpath(workspacePath(value)); - 读取本地文件ui/adapter.mjs:62
await client?.close();client=null;catalog=null;editorRoot=bundled;config=JSON.parse(await readFile(join(bundled,'assets.json'),'utf8')); - 写入本地文件ui/adapter.mjs:100
const folder=join(workspace,'craft-exports'); await mkdir(folder,{recursive:true}); - 读取本地文件ui/adapter.mjs:101
const actual=await realpath(folder), rel=relative(await realpath(workspace),actual); - 写入本地文件ui/adapter.mjs:104
await writeFile(path,bytes,{flag:'wx'}); saved.push(path); - 写入本地文件ui/bootstrap.mjs:24
if(type==='5')await mkdir(p,{recursive:true}); - 写入本地文件ui/bootstrap.mjs:25
else if(type==='0'||type===''){await mkdir(dirname(p),{recursive:true});await writeFile(p,tar.subarray(offset+512,offset+512+size),{flag:'wx'});} - 写入本地文件ui/bootstrap.mjs:25
else if(type==='0'||type===''){await mkdir(dirname(p),{recursive:true});await writeFile(p,tar.subarray(offset+512,offset+512+size),{flag:'wx'});} - 读取本地文件ui/bootstrap.mjs:33
try{for(const [name,hash]of Object.entries(files)){if(digest(await readFile(safePath(root,name)))!==hash)return false;}return Object.keys(files).lengt - 发起网络请求ui/bootstrap.mjs:37
const response=await fetch(lock.url,{signal:AbortSignal.timeout(90000)});if(!response.ok)throw Error('Runtime download HTTP '+response.status); - 读取本地文件ui/bootstrap.mjs:42
const here=dirname(fileURLToPath(import.meta.url));const lock=JSON.parse(await readFile(join(here,'runtime.lock.json'),'utf8')); - 写入本地文件ui/bootstrap.mjs:45
const base=join(process.env.LOCALAPPDATA||join(homedir(),'.cache'),'full-aigc-plugins',lock.name);await mkdir(base,{recursive:true}); - 写入本地文件ui/bootstrap.mjs:52
try{await rename(temp,runtime);}catch(error){if(!(await verifyRuntime(runtime,lock.files))){throw error;}} - 删除本地文件递归ui/bootstrap.mjs:53
}finally{await rm(temp,{recursive:true,force:true});} - 读取本地文件ui/bootstrap.mjs:59
const here=dirname(fileURLToPath(import.meta.url)),lock=JSON.parse(await readFile(join(here,'runtime.lock.json'),'utf8')); - 执行外部命令来自配置/变量ui/bootstrap.mjs:61
const runtime=await locateRuntime(),child=spawn(process.execPath,[join(runtime,'adapter.mjs'),...process.argv.slice(2)],{stdio:'inherit'}); - 读取本地文件ui/bootstrap.mjs:65
const catalogBytes=await readFile(join(here,'catalog.lock.json'));if(lock.catalogSha256&&digest(catalogBytes.toString('utf8').replaceAll('\r\n','\n')) - 执行外部命令来自配置/变量ui/bootstrap.mjs:76
const runtime=await locateRuntime();child=spawn(process.execPath,[join(runtime,'adapter.mjs')],{stdio:['pipe','pipe','inherit'],env:process.env,cwd:pr - 读取本地文件ui/bootstrap.mjs:109
if(process.argv[1]&&await realpath(process.argv[1])===await realpath(fileURLToPath(import.meta.url)))main().catch(error=>{console.error('Craft editor - 写入本地文件ui/build.mjs:3
await mkdir('dist',{recursive:true}); - 读取本地文件ui/editor-update.mjs:66
const old=JSON.parse(await readFile(join(bundled,'tools.json'),'utf8')),next=JSON.parse(files.get('tools.json')); - 写入本地文件ui/editor-update.mjs:83
const dir=join(cacheRoot,name);await mkdir(dir,{recursive:true,mode:0o700}); - 读取本地文件ui/editor-update.mjs:85
try{archive=await readFile(archivePath);if(digest(archive)!==release.sha256)throw Error('cache checksum mismatch');cached=true;}catch(e){if(e.code!==' - 读取本地文件ui/editor-update.mjs:93
if(!(await lstat(dest)).isDirectory()||(await lstat(dest)).isSymbolicLink())throw Error('Invalid cache directory'); - 读取本地文件ui/editor-update.mjs:94
for(const [p,b] of files){const f=join(dest,p);if((await lstat(f)).isSymbolicLink()||digest(await readFile(f))!==digest(b))throw Error('cache file che - 读取本地文件ui/editor-update.mjs:94
for(const [p,b] of files){const f=join(dest,p);if((await lstat(f)).isSymbolicLink()||digest(await readFile(f))!==digest(b))throw Error('cache file che - 读取本地文件ui/editor-update.mjs:98
try{await lstat(dest);throw Error('Incomplete cache');}catch(check){if(check.code!=='ENOENT')throw check;} - 写入本地文件ui/editor-update.mjs:100
for(const [p,b] of files){const out=join(staging,p);await mkdir(dirname(out),{recursive:true});await writeFile(out,b,{flag:'wx'});} - 写入本地文件ui/editor-update.mjs:100
for(const [p,b] of files){const out=join(staging,p);await mkdir(dirname(out),{recursive:true});await writeFile(out,b,{flag:'wx'});} - 写入本地文件ui/editor-update.mjs:101
try{await rename(staging,dest);staging=null;}catch(e){if(!['EEXIST','ENOTEMPTY'].includes(e.code))throw e;throw Error('Concurrent update; retry next s - 写入本地文件ui/editor-update.mjs:103
if(!cached)await writeFile(archivePath,archive,{flag:'wx'}).catch(e=>{if(e.code!=='EEXIST')throw e;}); - 删除本地文件递归ui/editor-update.mjs:105
}catch(e){return fallback(e.message);}finally{if(staging)await rm(staging,{recursive:true,force:true});} - 发起网络请求ui/host.mjs:5
const response=await fetch('rpc',{method:'POST',headers:{'Content-Type':'application/json','X-Craft-Page':pageId},body:JSON.stringify({method,params}) - 读取本地文件ui/package-editor.mjs:9
const config=JSON.parse(await readFile(join(source,'source.lock.json'),'utf8')); - 读取本地文件ui/package-editor.mjs:10
const pkg=JSON.parse(await readFile(join(source,'../plugin.json'),'utf8')); - 读取本地文件ui/package-editor.mjs:12
const exists=async p=>access(p).then(()=>true,()=>false); - 读取本地文件ui/package-editor.mjs:14
const toolCatalog=await readFile(join(site,'tools.json')); - 读取本地文件ui/package-editor.mjs:15
let html = await readFile(join(site, "index.html"), "utf8"); - 读取本地文件ui/package-editor.mjs:18
const wasmFiles = (await readdir(site)).filter((name) => name.endsWith(".wasm")); - 写入本地文件ui/package-editor.mjs:21
await mkdir(join(dist, "assets"), { recursive: true }); - 写入本地文件ui/package-editor.mjs:22
await writeFile(join(dist, "tools.json"), toolCatalog); - 写入本地文件ui/package-editor.mjs:31
await writeFile(join(dist, "assets", `${id}.gz`), gzipSync(bytes, { level: 9 })); - 写入本地文件ui/package-editor.mjs:53
await writeFile(join(dist, "assets.json"), JSON.stringify(manifest, null, 2) + "\n"); - 写入本地文件ui/package-editor.mjs:82
await writeFile(join(dist, "panel.html"), html); - 写入本地文件ui/package-editor.mjs:116
await writeFile(join(dist, "licenses/JAVASCRIPT-NOTICES.txt"), jsNotices.join("\n")); - 读取本地文件ui/prepare.mjs:7
const lock=JSON.parse(await readFile('editor.lock.json','utf8')); - 读取本地文件ui/prepare.mjs:9
const data=await readFile(join(resolve(source),path)); - 读取本地文件ui/prepare.mjs:12
const assets=JSON.parse(await readFile(join(source,'assets.json'),'utf8')); - 读取本地文件ui/prepare.mjs:14
try{await access('dist/editor');throw Error('dist/editor already exists; use a fresh build destination rather than replacing a live editor');}catch(e) - 写入本地文件ui/prepare.mjs:15
await mkdir('dist',{recursive:true}); - 写入本地文件ui/prepare.mjs:17
for(const path of Object.keys(lock.files)){const dest=join('dist/editor',path);await mkdir(resolve(dest,'..'),{recursive:true});await cp(join(source,p - 读取本地文件ui/runtime/assets.mjs:22
const root = await realpath(workspace); - 读取本地文件ui/runtime/assets.mjs:23
const file = await realpath(resolve(root, requested)); - 读取本地文件ui/runtime/assets.mjs:27
const info = await stat(file); - 读取本地文件ui/runtime/assets.mjs:30
const bytes = await readFile(file); - 读取本地文件ui/runtime/assets.mjs:43
const bytes = gunzipSync(await readFile(resolve(directory, `${id}.gz`)), { - 读取本地文件ui/runtime/automation.mjs:23
const root = await realpath(workspace); - 读取本地文件ui/runtime/automation.mjs:33
const canonical = await realpath(existing); - 读取本地文件ui/runtime/automation.mjs:35
if (existing === target && !(await stat(canonical)).isFile() && !(allowDirectory && (await stat(canonical)).isDirectory())) throw new Error("Output is - 写入本地文件ui/runtime/automation.mjs:50
await mkdir(dirname(target), { recursive: true }); - 写入本地文件ui/runtime/automation.mjs:57
if (exclusive) await link(temp,target); else await rename(temp, target); - 删除本地文件ui/runtime/automation.mjs:58
} finally { await unlink(temp).catch((error) => { if (error.code !== "ENOENT") throw error; }); } - 读取本地文件ui/runtime/automation.mjs:59
return relative(await realpath(workspace), target).split(sep).join("/"); - 读取本地文件ui/runtime/automation.mjs:63
const catalog = JSON.parse(await readFile(resolve(directory, "tools.json"), "utf8")); - 读取本地文件ui/runtime/automation.mjs:95
try { info = await stat(target); } catch (error) { if (error.code !== "ENOENT") throw error; } - 读取本地文件ui/runtime/extract-catalog.mjs:12
res.end(await readFile(path)); - 写入本地文件ui/runtime/extract-catalog.mjs:25
await writeFile(output, JSON.stringify(tools, null, 2) + "\n"); - 读取本地文件ui/runtime/server.mjs:12
const config = JSON.parse(await readFile(join(directory, "assets.json"), "utf8")); - 读取本地文件ui/runtime/server.mjs:30
text: await readFile(join(directory, "panel.html"), "utf8"), - 读取本地文件ui/web-adapter.mjs:12
const config=JSON.parse(await readFile(join(here,'config.json'),'utf8')); - 读取本地文件ui/web-adapter.mjs:20
const root=await realpath(workspace), candidate=resolve(root,path), rel=relative(root,candidate); - 写入本地文件ui/web-adapter.mjs:26
current=join(current,part);await mkdir(current,{recursive:true}); - 读取本地文件ui/web-adapter.mjs:27
const actual=await realpath(current), r=relative(root,actual); - 读取本地文件ui/web-adapter.mjs:32
const actual=await realpath(candidate), r=relative(root,actual); - 读取本地文件ui/web-adapter.mjs:52
if(args.workspace && resolve(args.workspace)!==workspace){if(opened)throw Error('Save and close this instance before changing workspace');workspace=aw - 读取本地文件ui/web-adapter.mjs:58
const data=await readFile(await scoped(args.path));if(data.length>50*1024*1024)throw Error('Input exceeds 50 MiB'); - 写入本地文件ui/web-adapter.mjs:65
await writeFile(target,bytes,{flag:'wx'});return ok({path:target,bytes:bytes.length}); - 写入本地文件ui/web-adapter.mjs:90
await writeFile(target,bytes,{flag:'wx'});res.setHeader('Content-Type','application/json');res.end(JSON.stringify({path:target})); - 发起网络请求ui/web-connect.mjs:6
const post=async(route,data)=>{const r=await fetch(new URL(route,base),{method:'POST',headers:{'Content-Type':'application/json','X-Craft-Page':pageId - 发起网络请求ui/web-connect.mjs:47
const r=await fetch(new URL('poll',base),{headers:{'X-Craft-Page':pageId}});if(!r.ok){window.__craftWebAdapter.error='Editor already active in another
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
制作可编辑的分层海报与合成图,再导出 PSD 或平面图像。
README
由 DeepSeek 最新模型翻译生成PhotoCraft Use
插件市场导航
本插件所属分类:AIGC 内容创作。
| 分类 | 插件市场入口 | 用途 |
| 全栈开发 | Full Stack Plugins | 架构与 UI 设计、代码理解、质量检查、代码审查、流程治理与服务器运维 |
| AIGC 内容创作 | Full AIGC Plugins | 图像、视频、音频、音乐、3D 与多模态内容创作 |
PhotoCraft Use——图层化图像编辑
制作可编辑的 .pcraft 海报、封面与合成:图层、蒙版、选区、局部调整、修饰、文字与尺寸变体,并导出 PSD 或平面图时记录损失。
项目定位
PhotoCraft Use 让位图设计保持可编辑。产品、背景与文字是有稳定 ID 和蒙版引用的独立图层;局部修饰会对照“不得改动”的像素检查;尺寸变体从源工程派生,而不是覆盖源工程。
原生 .pcraft 工程是交付事实源。压平的 PNG,或悄悄丢掉必要特性的 PSD,都不算设计交付。PSD 支持按必要特性把关:一旦观察到缺特性、降级或未知,除非你明确接受已记录的损失,否则会阻止成功。
展开完整文档(README 剩余 1 节)
PhotoCraft Use
插件市场导航
本插件所属分类:AIGC 内容创作。
| 分类 | 插件市场入口 | 用途 |
| 全栈开发 | Full Stack Plugins | 架构与 UI 设计、代码理解、质量检查、代码审查、流程治理与服务器运维 |
| AIGC 内容创作 | Full AIGC Plugins | 图像、视频、音频、音乐、3D 与多模态内容创作 |
PhotoCraft Use——图层化图像编辑
制作可编辑的 .pcraft 海报、封面与合成:图层、蒙版、选区、局部调整、修饰、文字与尺寸变体,并导出 PSD 或平面图时记录损失。
项目定位
PhotoCraft Use 让位图设计保持可编辑。产品、背景与文字是有稳定 ID 和蒙版引用的独立图层;局部修饰会对照“不得改动”的像素检查;尺寸变体从源工程派生,而不是覆盖源工程。
原生 .pcraft 工程是交付事实源。压平的 PNG,或悄悄丢掉必要特性的 PSD,都不算设计交付。PSD 支持按必要特性把关:一旦观察到缺特性、降级或未知,除非你明确接受已记录的损失,否则会阻止成功。
状态与版本
0.1.0-dev.20261019.1 · 开发预览 · docs/verification/release-candidate.json → releaseStatus: BLOCKED、installableDevelopmentCandidate: true。
当前插件:0.1.0-dev.20261019.1;技能源:0.1.0-dev.60;插件与技能保持独立版本。
| Identity | Current value |
| Metadata version | 0.1.0-dev.20261019.1 |
| Skills source | photocraft-skills / 0.1.0-dev.60 |
安装
前置条件
- macOS arm64,且公开脚本需要 PATH 上有 Python 3.11 或更新版本。
- 内嵌编辑器 MCP 服务需要 Node.js 24.15 或更新版本(harness 包限定 <25)。Git 检出没有 ui/dist/,需先按 ui/README.md 构建,或安装自带该目录的发布包。
- 固定的维护版 PhotoCraft CLI 在首次获得授权时安装到用户数据目录(--runtime-home 或 CRAFT_RUNTIME_HOME 可迁移)。不需要 sudo,不修改 PATH。
- 首次使用需要网络或已核验缓存。离线使用需要锁定归档或完好的已核验缓存。
安装插件
codex plugin marketplace add full-aigc-plugins/photocraft-plugin
codex plugin add photocraft@full-aigc-plugins-photocraft
docs/verification/release-candidate.json 记录 installableDevelopmentCandidate: true 与 releaseStatus: BLOCKED;project-status.json 保持 marketplaceEligible: false。
仓库带有 Codex(.codex-plugin/)、Claude Code(.claude-plugin/)、ZCode(.zcode-plugin/)与 Kimi(kimi.plugin.json)清单;在宿主验收出现之前,supportedPluginHosts 保持为空。
快速开始
: "${HARNESS_DIR:?请设为已加载 photocraft-harness 的 SKILL.md 所在目录}"
python3 -I -B "$HARNESS_DIR/scripts/bootstrap.py"
python3 -I -B "$HARNESS_DIR/scripts/commands.py" list --filter QUERY
然后直接说明你要的成品,例如“把这个商品抠到干净的白底上,并保持工程可编辑”。默认走 headless;桌面桥接需要单独的连接与能力验证。
flowchart LR
A[商品图、文案、品牌] --> B[photocraft-use 规划图层]
B --> C[固定运行时与原生 .pcraft]
C --> D[蒙版、选区、局部调整]
D --> E[重开并核对图层]
E --> F[PSD 或平面导出与损失记录]
可完成的工作
| 工作流 | 内置路径 | 可验证输出 |
| 海报或社交封面 | photocraft-use、photocraft-social-cover | 文字、商品与背景仍可编辑的重开 .pcraft |
| 商品抠图 | photocraft-product-cutout | 透明或白底输出,以及可编辑工程 |
| 老照片修复 | photocraft-restore-old-photo | 已检查人物特征与损伤区域的修复工程 |
| 扫描件清理 | photocraft-repair-scan | 已校正倾斜、去尘并记录纸张底色的扫描件 |
| 人像精修 | photocraft-retouch-portrait | 自然观感,且非目标区域已核对 |
| 批量交付变体 | photocraft-batch-image-delivery | 同尺寸同格式的输出,以及带文件名的联系表 |
| PSD 交换 | photocraft-cli-export | 必要特性通过校验的 PSD,并记录损失 |
| 在内嵌编辑器中修改 | photocraft-editor MCP 工具 | 保存后重开并检查的 .pcraft |
技能
| 技能 | 作用 |
| photocraft-use | 默认入口:可编辑 .pcraft 海报、封面与合成;安装维护版 CLI |
| photocraft-cli | 查询原生命令、参数与能力;执行明确指定的公共命令 |
| photocraft-cli-setup | 首次安装、摘要校验、版本检查与缺失运行时排障 |
| photocraft-cli-project | 创建、打开、保存与复制 .pcraft;色彩模式与位深 |
| photocraft-cli-layers | 商品、背景、文字与图层组;层级与混合 |
| photocraft-cli-selection | 选区、反选、羽化与局部选择 |
| photocraft-cli-masks | 图层蒙版、矢量蒙版与剪贴合成 |
| photocraft-cli-adjustments | 调整图层与已授权的局部颜色调整 |
| photocraft-cli-retouch | 对已授权图像做修复、克隆、画笔与局部修饰 |
| photocraft-cli-text | 文字图层、字体、段落与布局 |
| photocraft-cli-resize | 图像与画布尺寸、裁边、扩展、旋转与封面变体 |
| photocraft-cli-filters | 局部滤镜、背景虚化、颗粒与输出锐化 |
| photocraft-cli-export | 从原生工程导出 PSD、PNG 或其他交换文件 |
| photocraft-harness | 唯一执行通道:bootstrap、commands、workflow、任务会话 |
| photocraft-editor | 打开真实编辑界面,一次会话内完成全部编辑 |
| photocraft-product-cutout | 电商抠图:透明底、白底与边缘清理 |
| photocraft-repair-scan | 扫描件清理:歪斜、污点与纸张底色 |
| photocraft-restore-old-photo | 划痕、折痕、褪色与局部缺损 |
| photocraft-retouch-portrait | 自然人像的皮肤、瑕疵、肤色与光影 |
| photocraft-social-cover | 封面构图、标题与按平台裁切 |
| photocraft-batch-image-delivery | 统一尺寸、色彩与格式,或生成带文件名的联系表 |
| photocraft-scenario-playbooks | 覆盖 6 个图像场景的带真实参数手册 |
斜杠命令
Claude Code 清单(com.anthropic.claude-code/)提供 22 个命令,每个技能一个,并附带 SessionStart 指引钩子。命令只转交对应技能,不会自行启动任何原生实例。
| 命令 | 作用 |
| /photocraft-use | 完整任务入口与最短技能链 |
| /photocraft-editor | 内嵌编辑器会话 |
| /photocraft-harness | 受管理执行通道与任务控制 |
| /photocraft-scenario-playbooks | 按当前问题选择场景手册 |
| /photocraft-cli、/photocraft-cli-{project,layers,selection,masks,adjustments,retouch,text,resize,filters,export,setup} | 命令级技能族,每个技能一个命令 |
| /photocraft-{restore-old-photo,retouch-portrait,product-cutout,repair-scan,social-cover,batch-image-delivery} | 场景入口,每个技能一个命令 |
MCP 工具
mcp.json 只声明一个 stdio server:photocraft-editor,从插件根目录以 node ui/bootstrap.mjs 启动。
| 工具 | 用途 |
| open_editor | 为绝对路径 workspace 打开编辑器 |
| open_browser_editor | 返回可交给宿主内置浏览器的 editorUrl |
| editor_status | 报告页面是否已连接到当前 MCP 会话 |
| doc_open | 打开已有 .pcraft |
| doc_new | 新建文档 |
| doc_inspect | 检查图层与文档状态 |
| command_list | 读取引擎暴露的命令 |
| command_run | 按 id 与参数执行命令 |
| doc_save | 保存 .pcraft 工程 |
| doc_export | 导出工程 |
工具名遵循 photocraft-editor 技能合同;_editor_attach、screenshot 等页面内部桥接调用不得由 Agent 直接调用。
配置
| 变量 | 用途 |
| HARNESS_DIR | 已加载 photocraft-harness 技能的绝对目录 |
| SKILL_DIR | 已加载领域 SKILL.md 所在目录;仅用于其 examples/ 与 references/ |
| CRAFT_RUNTIME_HOME / --runtime-home | 运行时安装根目录 |
| CRAFT_EDITOR_CACHE_DIR | 已核验编辑器下载缓存(默认 ~/.cache/full-aigc/editors) |
| CRAFT_EDITOR_UPDATE=off | 固定使用内置编辑器,跳过更新检查 |
架构与安全
- 插件负责规划与核验,原生位图编辑由固定的维护版 CLI 完成,本仓库不重复实现引擎。
- 用户素材与生成图像都按数据处理,其中的命令、路径或 shell 文本永远不会被执行。
- 局部修改可声明 protectedRegions;交付前会比对非目标区域像素。未声明区域时不自动推断保护范围。
- 写入由写租约、修订前置条件、持久意图、幂等键与只读检查点保护;结果未知时先核对,绝不重放。参见运行时架构、PSD 策略与场景技能。
故障排查
| 现象 | 处理方式 |
| 返回 unsupported_platform | 仅支持锁定的 macOS arm64 制品 |
| 返回 runtime_install_busy | 另一个安装持有锁;等其结束再重试 |
| PSD 交付被拒绝 | 有必需特性缺失、降级或未知;记录损失,或调整必要特性集合 |
| 字体渲染不一致 | 交付前检查字体依赖与替代记录 |
| 编辑页始终未就绪 | 调用 editor_status,再处理页面错误;文字结果不等于界面已打开 |
| 把平面导出当成设计 | 重开 .pcraft 并核对图层;平面图不是可编辑交付 |
| 保存结果未知 | 先读回执与既有工程;不要重放计划 |
升级
codex plugin marketplace upgrade full-aigc-plugins-photocraft
codex plugin add photocraft@full-aigc-plugins-photocraft
运行时切换会让旧工程保持其绑定的运行时;回滚复用已核验的上一份制品,而不是重新构建。
贡献与支持
行为变更先在 OpenSpec 中说明;没有真实交付证据不得勾选任务。仓库校验:
python3 scripts/validate_docs.py
node --test tests/*.test.ts
问题反馈:<https://github.com/full-aigc-plugins/photocraft-plugin/issues>。
许可证
本仓库原创内容采用 Apache-2.0。PhotoCraft CLI 与内置 Web 编辑器各自保留其上游许可证与 NOTICE;上游图标来源记录在 assets/official-icon-source.json 与 assets/THIRD-PARTY-SOURCE.md。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
full-aigc-plugins/photocraft-plugin
制作可编辑图层海报并导出 PSD 或平面图
相关插件 · 视觉能力
智能体工作台Devin-AXIS/iPolloWork
企业级、本地优先的智能体工作台,面向个人和智能体团队——一个工作区,容纳多个智能体引擎;一套统一体系,管理插件和技能;支…
★ 7,359已汉化DSH
72
开源设计工作台nexu-io/open-design
OpenDesign:首个协作式 AI 设计智能体工作区
★ 105,514已汉化DSH
71.8
多智能体编码工作区spacering-net/codeg
多智能体协作的 AI 编程工作区:汇总 Claude Code、Codex、OpenCode、Pi、Grok Build 等来源的会话。支持桌面应用、自托管服务器或 Docker。
★ 4,289已汉化DSH
71.6
GPT 图像提示词库freestylefly/awesome-gpt-image-2
Prompt as Code | GPT Image 2 / 2.5 提示词与案例库,530+ 个案例、20+…
★ 34,534已汉化DSH
71.5
架构图生成器tt-a1i/archify
把你想理解、规划或分享的事,变成可以互动的可视化作品。
★ 92,675已汉化DSH
71.2
AI 用量桌面小部件Javis603/token-monitor
EN | 简 | 繁 | KO | JA | PT
★ 3,522已汉化DSH
71
这是你的仓库?认领条目