Skip to content
dsh-market Browse plugins GitHub 中文

netori/galfree

Ren'Py galgame production workbench: a host-side project service (write gateway plus git snapshots, a dialogue/scene dialect parser, derived progress, human-only review stamps, pinned-SDK validation and playtest, local publish) behind one seam, driven by an embedded web panel and agent tools.

Stars ★ 1 Category Development & Runtime Listed 2026-09-19 npm dsh-galfree

Install

Inside DeepSeek Harness, with dsh-market

dsh plugin --profile web add dshmarket

Or from the command line

dsh plugin --profile web add dsh-galfree

Installing runs third-party code with your own permissions — it can read your files, use your credentials and reach the network. Review the source first, and pin a commit (github:owner/repo#sha) when you can.

README

This plugin publishes its README in Chinese only.

在 DSH 里自动化制作 Ren'Py galgame 的全流程工作台插件。 领域语言见 CONTEXT.md,架构决策见 docs/adr/, 环节零接缝契约见 docs/contracts/stage-zero.md。

状态

  • ✅ 环节零(T1–T7):项目服务接缝、写网关、git 快照、方言子集解析器、 假/真校验回路、钉版 SDK 供给、推导进度 + 审读戳、试玩控制、工作台
  • ✅ 工作台面板:舞台板(场景 × 素材槽 × 印章)、文件树筛选、文件内容预览、 快照历史 / diff / 回滚、项目切换、SDK 供给卡
  • ✅ 新建项目的父目录走宿主目录选择接缝(ctx.directoryPicker):本机直接开系统 文件夹选择框,远程/无显示会话用面板内目录浏览器,没装后端就隐藏入口;不选则回落到 设置里的默认父目录,表单里写明"将创建到哪"
  • ✅ 两处接缝已备、入口缺失的补齐(经发起人确认):素材槽盖审读戳(接缝早有 stampSlot)、快照回滚(接缝早有 snapshotRollback)
  • ✅ 路由适配层契约测试(src/service/routes.slow.test.ts,慢带):状态码映射 / 方法守卫 / SSE 帧 / 工作台实际调用的每个端点 —— 是接缝纪律的一处记录在案的例外, 理由见 docs/contracts/stage-zero.md 测试纪律节
  • ✅ T8(角色登记簿 + 素材槽账本):.studio/characters.json 接缝 CRUD(经网关写); 槽清单从 .rpy 图像引用派生、账本只往上挂制作信息;悬空引用(两个方向)进板并 定位到引用它的那条语句;工作台新增「素材板」(槽视图 + 角色视图,纯渲染)
  • ✅ T9(设定集工作周期):.studio/bible/ 主题/世界观/章节 + 对登记簿的引用; 人写大纲逐字导入、原文即权威(fingerprint 对不上就如实报);「设定定稿」戳只人可盖、 改动自动待复审;generationContext 只给定稿版(未定稿 409);工作台新增「设定集」卡
  • ✅ T10(逐场剧本生成):generateScene 一个写批 = 一个快照,写完当场判定并原样交回 (路径 / 解析 / 校验 / 问题 / 板快照);生成物固定落 game/scenes/<label>.rpy,手写文件 不越界(要重生成先由人「搬进生成目录」);agent 工具 galfree_generate_scene / galfree_project_status;.rpy 读取改递归(与真 SDK lint 同口径)
  • ✅ T11(对话流结构化编辑器)+ T12(分支图):表单逐行改对白/图像引用,只改被指向的 那一行(git diff 断言最小化);源文本模式并存,两路同走网关;手写文件与子集外场景的 表单编辑如实拒绝(源文本仍可改);分支图 = 派生骨架的只读导航,点节点定位到编辑器
  • ✅ T13(整线组装试玩):项目级完整性推导(入口 / 孤立场景 / 结局可达)+ 把全局问题 定位到场景;从此场试玩(副本里覆写入口,用户项目不动;目标场不存在就崩 → 信号 可红,慢带有真 SDK 断言);traceback 摘要入状态、板可见、agent 可读
  • ✅ T14(图像渠道 + 任务队列,Host 直连):插件自有渠道设置(端点 / 密钥明文存本机设置 / 模型目录带能力声明);任务 = 全结构化一级对象(目标槽、登记簿上下文、参考链、输出路径), 状态机 queued → running → awaiting-review|failed + 只追加的重试历史;产物一律 经写网关落盘(网关类型拓宽到二进制,版本戳按字节)→ 自动快照 → 槽位推导立刻转「待复审」; 不支持参考链就自动降级文生图并附说明(丢掉的参考图逐张列出,绝不静默)
  • ✅ T15(出图操作与素材板动作面):素材板上「补全全部待填」/「生成此槽」/「重 roll(可改词)」, 进度徽标与只追加的重试历史(含被替换那一版的指纹 → 能从快照找回上一张); agent「美术指导」工具面 5 个(galfree_image_channel / galfree_art_queue / galfree_generate_image / galfree_reroll_image / galfree_fill_missing_art)—— 与面板同一条队列、同一份推导,没有第二管线
  • ⏳ 这一行之后的历史(T16 参考链 … T32 声音锚)不再在这份清单里逐票罗列 —— 现状以 CONTEXT.md 的「进行中」节 + docs/handoff-*.md(每票一份交接)为准, 接缝契约在 docs/contracts/stage-zero.md。三句话概括:T16 参考链(同一张脸)、 T17–T18 音频接线与本地发布、T19–T22 工具面/指引/nextActions/preset、 T23–T24 中文字体与试玩不卡人、T25–T32 三条生成线(图像已有;音乐/语音各一条渠道; 语音的声音锚 = 每个角色一份参考样本)+ 封面 + 界面换皮 + 快照回滚。
  • ✅ T37(0.1.2)· 适配 DSH 0.1.7:插件原来那套设置模型(ctx.settings.register + 客户端 settingsScope + settings.plugin.item)在 0.1.7 里被整体删掉, 于是 apply 第一句就抛 —— 表现就是"插件装不上"。现在:Host 半把 Config 每个字段 声明为 .volatile() 并现读现取引用;客户端走 ctx.configForms 把设置页挂在 Plugins 页的 plugins.bundle.config 上。schema 库随之换成宿主的 @deepseek-ai/schemastery。要求宿主 ≥ 0.1.7;在 0.1.7 上真机跑通过 (路由族全 200、客户端 bundle 进启动载荷、16 个字段逐个带 volatile), 细节与"没验到什么"见 docs/handoff-2026-09-24-t37.md。
  • ✅ T38 · 「Galgame 制作」preset 换了载体:0.1.7 删掉了 $DSH_HOME/.agent-presets/<id>/ 目录机制(随包文档原话:"Nothing reads that directory any more"),旧的"拷四个文件进去"装法静默失效。preset 现在是随插件 bundle 一起发的 声明行 —— 装插件就有这个模式,不用再拷任何东西。

工程

src/
  index.ts            Host 半(cordis apply:路由/设置/装配)
  client/             工作台 Client 半(sidebar.panellist + main 面板)
    panel.tsx           装配层:拉接缝状态、分发、把人的动作送回去
    stage-board.tsx     舞台板(读 scene.marks / stampable —— 不自己判断)
    file-inspector.tsx  文件内容 / 快照历史 / diff / 回滚
    directory-picker.tsx 目录选择(OS 选择框 / 面板内浏览器,按宿主能力)
    project-switcher.tsx 注册表激活位切换
    sdk-card.tsx        钉版 SDK 供给
    ui.tsx              Chip / 印章 / 提示条 / diff 视图
    types.ts            视图类型(与 routes 响应形状一一对应)
    panel.module.css    设计令牌 + 版式(配色全走宿主 --dsw-* token)
  routes.ts           /api/galfree 薄适配器
  service/            ★ 项目服务(seam)——独占逻辑全在这里
    project-service.ts   注册表 + 模板新建 + 全部环节方法
    progress.ts          推导引擎(含 deriveSceneMarks:舞台标记的唯一出处)
    stage.ts             舞台层:图片定义 + 立绘站位(生成物 game/zz_galfree_stage.rpy)
    text-color.ts        演出字色:调色板 / 对比度闸门 / 分寸(全部是 warning)
    images.ts            图像子系统:渠道/模型能力/适配器/降级判定/任务账本(纯逻辑)
    write-gateway.ts     串行 CAS 原子批 + 二进制写 + 外部观察(唯一写通道)
    snapshot.ts          写批后 git commit(作者 GALFree,永不 push)
    rpy/                 方言子集解析器 + 分支骨架派生(纯函数)
    validation/          校验回路契约 + 假验证器 + 真 SDK 适配器 + 合成端口
    sdk-provision.ts     钉版 SDK 下载状态机(校验和钉死)
    stamps.ts playtest.ts registry.ts template.ts hash.ts
docs/contracts/       接缝契约(dialect-subset.md / stage-zero.md)

常用命令

npm run typecheck      # tsc --noEmit
npm test               # 快集成带(无网络、无真 SDK;全在 ProjectService 接缝上)
npm run test:slow      # 慢集成带(真钉版 SDK lint/compile + 路由适配层契约;发版前必跑)
npm run build          # lib/index.js(ESM host)+ lib/client.js(web bundle)

安装(人工验收)

装载后侧边栏出现「GALFree 工作台」入口。三条装法,推荐第一条。

① 从插件市场装 / 预构建包(不需要 git,也不需要构建授权)

发行物挂在 GitHub Release 上,资产名带版本号、链接钉住 tag (0.1.0 用的是"不带版本号 + latest/download"那种写法;两者都会在 URL 不变的情况下换成 后一版的字节,条目看起来没改却在装不同的代码,所以 0.1.1 起改钉 tag):

dsh plugin --profile web add "https://github.com/netori/galfree/releases/download/v0.1.2/dsh-galfree-0.1.2.tgz"

⚠️ 0.1.2 起要求宿主 ≥ DSH 0.1.7。0.1.1 及更早那几版在 0.1.7 上装不上 (设置模型被整体替换,apply 第一句就抛)—— 如果你是从旧版升上来的,升级插件即可, 项目数据与 .studio/ 一个字节都不动。

实测(冷 store,桌面端自带的 pnpm 11.8.0):768ms 装完,lib/index.js / lib/client.js / cordis.patch.yml 全部就位,不跑任何安装期构建 —— 所以没有 allowBuilds 那一步,也不需要机器上装过 git (file: / tarball 依赖 pnpm 不跑 prepare,包里的 prepare 原样留着也无事)。

市场条目(dsh-market / 社区市场会读到的那条)在 market/netori__galfree.yml:url + name + category + 双语句描述 + tarball: 字段。市场的 installTargetFor() 规则是 repo 验过的 npm 包 > 作者提供的预构建 tarball > github:owner/repo —— 我们走的是第二条,所以市场给用户的安装目标就是上面这条 .tgz。 本地自检(复刻市场的 tarball 绑定校验 + latest/download 腐烂陷阱):

node scripts/check-market-entry.mjs market/netori__galfree.yml

⚠️ 条目进市场要往 awesome-dsh-plugin 提 PR(一个文件 = 全部投稿,见 market/README.md)。目前还没提 —— 所以现在市场里搜不到,得先用上面那条命令。

发到 npm 是另一条更省事的路(市场会优先用它,而且不用往 awesome-dsh-plugin 提条目): package.json 已经为此备好 repository 与 publishConfig(指向官方 registry), 只差一次 npm login + npm publish --access public。本机当前 npm whoami 未登录(ENEEDAUTH), 所以这一步要么在本机登录,要么交给 CI(见 market/README.md)。

② 从仓库源码装(会现场构建一次,要过 pnpm 的构建授权)

从仓库安装时它会现场构建一次(仓库里不带 lib/,package.json 的 prepare 负责构建;这是 DSH 对 git 插件的既定方式)。pnpm 默认拦下安装期构建脚本,所以第一次 会失败并打印一个 key —— 把那个 key 原样填进 profile 的 pnpm-workspace.yaml 的 allowBuilds,再装一次即可(~/.dsh/profiles/<profile>/pnpm-workspace.yaml)。

从仓库装,第一次一定失败 —— 那一步是设计如此(2026-09-19 冷 store 实测)

症状(原话,用桌面端自带的 pnpm 11.8.0 在一个空 store 的干净目录里复现):

[ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED] Failed to prepare git-hosted package fetched from
"https://codeload.github.com/netori/galfree/tar.gz/<commit>": The git-hosted package
"dsh-galfree@0.1.0" needs to execute build scripts but is not in the "allowBuilds" allowlist.

Add the package to "allowBuilds" in your project's pnpm-workspace.yaml to allow it to run scripts. For example:
allowBuilds:
  dsh-galfree@https://codeload.github.com/netori/galfree/tar.gz/<commit>: true

触发条件(实测出来的,不是推理):这条拦的是「第一次从 git 装一个 带 prepare 而不带 lib/ 的包」—— 也就是冷 store(cafs 里还没有这份已构建的产物)。 pnpm 对 git 依赖会先跑 prepare(它把 npm install → prepare → tsdown 真跑一遍); 构建脚本默认不许跑,于是它抛错而不是跳过,lib/ 就不会出现。 一旦这份构建产物进了 store,之后所有安装都直接复用、不再问授权 (实测:同一台机器、同一个 commit,冷 store 必红;而 store 里有了之后 allowBuilds 写不写都过、2.8 秒装完)。

修法:把 pnpm 打印的那一整行 key 逐字(带 @https://codeload… 与 commit 哈希) 填进 ~/.dsh/profiles/<profile>/pnpm-workspace.yaml,再装一次:

allowBuilds:
  'dsh-galfree@https://codeload.github.com/netori/galfree/tar.gz/<commit>': true

实测(冷 store + 这一条):构建自动跑起来(npm-install → prepare → tsdown 全在输出里), node_modules/dsh-galfree/lib/index.js 就位,装完。

⚠️ 只写 dsh-galfree: true 不行(冷 store 实测照样报同一个错)—— key 认的是带 commit 的完整依赖键;而且用 github:owner/repo 这种没钉 commit 的写法时, 上游一有新提交键里的哈希就变,同一个症状会再出现一次,照新打印的那行再填一条即可。

从仓库装不上时的另一条路:预构建 tarball(不需要对方装 git)

作者侧一条命令打出 320KB 的预构建包(里面已含 lib/ 与 cordis.patch.yml):

npm pack            # 产出 dsh-galfree-0.1.0.tgz

对方那侧(<tgz> 换成实际路径):

dsh plugin add file:<tgz>

实测两点,都值得记下:

  • 这条路完全不碰 git —— pnpm 对 file: / tarball 依赖不跑 prepare (包里的 prepare 原样留着也无事),于是没有构建授权那一步, 干净目录里 1.5 秒装完、lib/index.js 就位;
  • 仍会有 peer 警告(react / @deepseek-ai/* 由宿主供给),那是预期的,不是失败。

拿到的是解压好的目录就用 link:(实测 91ms 装完,同样不跑构建脚本、不需要 allowBuilds; 改代码重启宿主即可生效):

dependencies:
  dsh-galfree: link:E:/DSH_project/DSH_creator

tarball 里有什么(npm pack --dry-run 实列,共 8 个文件): package.json / lib/index.js / lib/client.js / cordis.patch.yml / README.md / LICENSE / docs/contracts/*.md。 仓库里没有 templates/ 这个目录 —— 新建项目用的界面文件与中文字体是装好之后从钉版 SDK (或按设置里的 sdkPath 指向的既有 SDK)拷进项目的,所以 tarball 里没有它是正常的, 不表示打包缺料。

自己改代码时不必依赖 prepare:在本仓库目录里跑

npm install && npm run build   # 产出 lib/,宿主加载的就是它

改完必须重建 + 重启宿主:宿主只在启动时载入 lib/index.js;面板是按需从磁盘取的, 所以只重建不重启会出现"面板有按钮、宿主没路由"。

设置的读法从 DSH 0.1.7 起换了 —— 插件的配置面长在 Plugins 页(侧边栏的「插件」)里 本 bundle 的「配置」入口上,不再在「设置 → 插件配置」下;插件也不再注册自己的设置命名空间。 原因是那套 API 已经不存在:ctx.settings.register(ns, schema, {base}) 与客户端的 settingsScope / settings.plugin.item 在 0.1.7 里一次都搜不到(这正是 0.1.1 在 0.1.7 上"装不上"的根因 —— apply 第一句就抛,工具面与面板跟着一起没有)。 字段与语义不变,三条生成线各自一条渠道 + 发布目录,全在这张配置页上。 每条渠道的字段是同一套四件(端点 / 密钥 / 渠道名 / 模型目录),只是前缀不同 —— 所以下表按前缀分成三族;镜像 那一列指的是"音频两族与图像族逐字同义"。

设置项 镜像 说明
imageBaseUrl — 图像渠道端点(OpenAI 兼容基址,如 https://api.example.com/v1);留空 = 没配那条渠道,对应动作会如实拒绝(不假装能出),也不拖累别条
imageApiKey — 渠道密钥。明文存在本机设置文档里(ADR-0010 的知情选择,与 dsh-imagegen 同风险面):它不进项目目录、不进快照、不进任务账本
imageChannelName — 渠道名(只在面板/账本里指认用)
imageModels — 模型目录(JSON 数组),每个模型要声明能力,否则按保守缺省
musicBaseUrl 同 imageBaseUrl 音乐生成渠道端点(与图像那条分开配:上游与协议不重叠)
musicApiKey 同 imageApiKey 音乐渠道密钥(明文存本机)
musicChannelName 同 imageChannelName 音乐渠道名
musicModels ⚠️ 多两栏必填 音乐的每条模型必须写 purpose: "music" 与 adapter —— 拼渠道会按 purpose 过滤,写错那一栏那条模型会被静默滤掉(表现是"配了、模型数是 0")
voiceBaseUrl 同 imageBaseUrl **语音(TTS)**渠道端点(与音乐那条分开配)
voiceApiKey 同 imageApiKey 语音渠道密钥(明文存本机)
voiceChannelName 同 imageChannelName 语音渠道名
voiceModels 同 musicModels 语音的每条模型要写 purpose: "voice" 与 adapter(同上,写错会被静默滤掉)
publishDir — 发布产物输出目录;留空 = 数据目录下的 publish/<项目名>。产物落在项目源树之外

adapter 填什么(按协议形状收,不按厂商收;先翻服务商文档那一页,或拉一次模型列表让面板推断):

渠道 adapter 什么形状
图像 (省略) OpenAI 兼容的 /images/generations(默认)
图像 async-task 提交拿任务 id → 轮询(查询串里带 id),如 new-api 系的 /v1/image/generations
音乐 / 语音 async-task sunoapi.org 那套(查询串轮询)
音乐 / 语音 async-task-rest 资源式 REST:任务 id 在路径里(GET …/tasks/{id})
语音 sync-http 本机 IndexTTS 那类:POST {base}/tts + {speaker, audio, text, lang},回 JSON 再去取(要同机读盘)
语音 openai-speech OpenAI 兼容的 /audio/speech,响应体直接是音频字节(硅基流动 / OpenAI / 多数兼容网关)
语音 mimo-chat-tts 小米 MiMo 那类:文本放 role:"assistant" 的消息里,音频在 choices[0].message.audio.data(base64)

不想从头填?配置页最上面有一排「渠道模板」 —— 点一下就把它那几个键填进对应的那一段 (端点 / 渠道名 / 模型目录)。两条纪律:模板不碰密钥(缺 key 会如实拦住,不会假装配好了)、 不自动保存(填完核对再按保存)。模板带抄写日期与"实测到什么程度",不构成推荐或代销; 充值入口指向站点自己的页面、不带推广码。

音频两条渠道的目录示例(音乐那族;语音把 purpose / 能力换成 voice 那套即可):

[
  { "id": "suno-generation", "purpose": "music", "adapter": "async-task-rest",
    "label": "Suno 文生曲", "note": "model=suno;version=v6;format=mp3",
    "capabilities": { "textToMusic": true, "instrumental": true } }
]

⚠️ note 是这家服务自己的参数(分号分隔的键值),不进通用条目:async-task-rest 读 submit / poll / model / version / format,sync-http 读 speaker / audio / lang, openai-speech 读 voice / format / speed,mimo-chat-tts 读 voice / format / label。 音频的能力缺省全为假(与图像相反:图像缺省文生图为真)—— 没声明就是不能干,不靠默认值许诺。

imageModels 示例(能力缺省口径:文生图/尺寸参数/b64 为真,参考链/图生图为假 —— 能力宁可少说,不能凭空许诺):

[
  { "id": "gpt-image-1", "label": "全能力",
    "capabilities": { "textToImage": true, "imageToImage": true, "referenceChain": true,
                      "aspectRatioParam": true, "b64Json": true } },
  { "id": "basic-model", "label": "只有文生图" }
]

⚠️ enabled / defaultProjectsRoot / sdkPath 这三项在 0.1.7 上不再是"别处可改": 旧版能改是沾了"宿主给已注册命名空间自动渲染表单"的光,0.1.7 起宿主不再渲染插件的配置 (autoGenerate 只是个描述字段)。三项都优雅降级(enabled 默认 true;新建项目表单本来 就让人显式选目录;sdkPath 只在要覆盖钉版 SDK 时才有用),要改就去 profile 的 cordis.patch.yml 里那一行 id: galfree 的 config。把三项做进那张卡是下一票的事。

硬约束(来自 ADR,改前先看契约文档)

  • 项目文件一切写走 Host 写网关;.rpy 唯一真相;.studio/ 永不复制叙述内容
  • 进度是推导的;审读戳只能由人盖,重生成自动清戳(指纹失效即待复审)
  • 不依赖 dsh-imagegen;SDK 用钉版;插件永不 push

Content from the project README on GitHub ↗

Comments

Comments live in GitHub Discussions. Sign in with GitHub to post or react.