From 2216c13e95c1e077fdebcc997b8ea4a83bdc5541 Mon Sep 17 00:00:00 2001 From: bigbrother666sh Date: Fri, 5 Jun 2026 12:33:12 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix=EF=BC=9Amain-agent=20hearbeat=20add?= =?UTF-8?q?=EF=BC=9A=20ir=20swcr=20register=20skill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +- addons/officials/README.md | 103 ++-- addons/officials/addon.json | 4 +- addons/officials/crew/ir/AGENTS.md | 12 +- .../crew/ir/skills/swcr-register/SKILL.md | 213 ++++++++ .../scripts/generate_code_doc.py | 436 ++++++++++++++++ .../scripts/generate_form_info.py | 486 ++++++++++++++++++ .../swcr-register/scripts/generate_manual.py | 272 ++++++++++ addons/officials/requirements.txt | 4 + config-templates/openclaw-aihubmix.json | 5 + config-templates/openclaw.json | 5 + 11 files changed, 1508 insertions(+), 39 deletions(-) create mode 100644 addons/officials/crew/ir/skills/swcr-register/SKILL.md create mode 100644 addons/officials/crew/ir/skills/swcr-register/scripts/generate_code_doc.py create mode 100644 addons/officials/crew/ir/skills/swcr-register/scripts/generate_form_info.py create mode 100644 addons/officials/crew/ir/skills/swcr-register/scripts/generate_manual.py diff --git a/README.md b/README.md index c9c3491c..59e84beb 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,13 @@ 🚀 **v5.5.0 更新** -- **从安装到出活,全程微信对话完成**:首次部署时自动安装官方微信插件,扫码绑定后,Wiseflow Main Agent 直接在微信上引导你完成业务背景采集、团队组建、渠道配置——不再需要编辑任何配置文件。 +- **从安装到出活,全程微信对话完成**:首次部署时自动安装官方微信插件,扫码绑定后,Wiseflow Main Agent 直接在微信上引导你完成业务背景采集、团队组建、渠道配置——不再需要编辑任何配置文件 - 如果只是想要一个个人助理、或者使用场景比较简单(crew 数量不超过 3 个),可以一直使用微信渠道,无需额外申请飞书开放平台或企业微信(后续如需扩展,Main Agent 也会给出申请和开通的详细指导) +- **Selfmedia Operator 增强**:新增微信公众号文章自动排版并推送至草稿箱、简单短视频制作(t2video)、高光时刻视频剪辑(highlight-clipper),现已支持 15 个国内外主流自媒体平台的发布能力(部分为pro版本提供) - **Designer 升级**:从"出图工具"重新定位为**系统性视觉设计体系构建者**,内置 `design-system-picker` 技能,预置 15 套品牌设计系统(覆盖 fintech / devtools / productivity / consumer / luxury / enterprise 等全品类),一键匹配风格后从零构建完整网页、APP 界面、品牌视觉体系 - **IT Engineer 升级**:新增SEO、ICP备案辅助、云服务资源管理能力,现在 Designer + IT Engineer 的技能组合可覆盖官网 / Landing Page 的完整流程——**设计 → 开发 → 部署(云计算)→ 备案(ICP)→ SEO** -- **Selfmedia Operator 增强**:新增微信公众号文章自动排版并推送至草稿箱、简单短视频制作(t2video)、高光时刻视频剪辑(highlight-clipper),现已支持 15 个国内外主流自媒体平台的发布能力(部分为pro版本提供) -- **商务拓展 + 投资人关系正式发布**:business-developer 继承 4.x 全部核心能力(指定信源监控、行业情报采集、社交媒体潜客挖掘),并新增业务介绍 PPT 制作能力;investor-relationship 预发布 +- **new crew: business-developer**:business-developer 继承 4.x 全部核心能力(指定信源监控、行业情报采集、社交媒体潜客挖掘),并新增业务介绍 PPT 制作能力 +- **new crew: investor-relationship**(预发布):软件著作权申报(材料准备、页面填写)、商业模式优化、商业思路记录、BP编写、项目申报、搜索并接触投资人 详见 [CHANGELOG.md](CHANGELOG.md) diff --git a/addons/officials/README.md b/addons/officials/README.md index a1eaea13..46a73a03 100644 --- a/addons/officials/README.md +++ b/addons/officials/README.md @@ -10,10 +10,17 @@ wiseflow 开源社区版本自带的官方 addon,在 wiseflow 默认全局技 |------|------|------------|----------| | `rss-reader` | RSS/Atom Feed 读取,支持订阅任意标准 feed 格式的内容源 | — | 全部 crew 可用 | | `siliconflow-img-gen` | 文生图、图片修改(SiliconFlow API) | `SILICONFLOW_API_KEY` | designer、selfmedia-operator | +| `pexels-footage` | Pexels 免版权图片/视频搜索与下载 | `PEXELS_API_KEY` | designer、selfmedia-operator | +| `pixabay-footage` | Pixabay 免版权图片/视频搜索与下载(Pexels 备选) | `PIXABAY_API_KEY` | designer、selfmedia-operator | +| `council` | 召集四方视角对商业模式、融资策略等关键决策进行结构化辩论 | — | **仅 ir**;其余 crew DENIED | | `connections-optimizer` | 人脉关系网络优化与拓展建议 | — | **仅 business-developer**;其余 crew DENIED | | `email-ops` | 批量邮件撰写与 SMTP 发送 | `SMTP_SERVER` `SMTP_USER` `SMTP_PASSWORD` | **仅 business-developer**;其余 crew DENIED | -| `pitch-deck` | 融资/商务演示文稿生成,支持读取 .pptx 文件内容 | — | **仅 business-developer**;其余 crew DENIED | +| `pitch-deck` | 融资/商务演示文稿生成,零依赖单文件 HTML 输出 | — | **仅 business-developer / ir**;其余 crew DENIED | +| `ppt-maker` | 从文稿内容生成专业 PPTX,支持模板/参考图风格提取、AI 配图 | — | **仅 business-developer / ir**;其余 crew DENIED | | `social-graph-ranker` | 社交图谱关键节点分析与排序 | — | **仅 business-developer**;其余 crew DENIED | +| `web-form-fill` | 网络表单填报,从信息搜集到浏览器填报的完整工作流 | — | **仅 ir**;其余 crew DENIED | +| `xhs-interact` | 小红书社交互动:发表评论、回复评论、点赞 | — | **仅 business-developer**;其余 crew DENIED | +| `xianyu-ops` | 闲鱼商品搜索、详情查看、私信会话管理与回复 | — | **仅 business-developer**;其余 crew DENIED | ## 2. Crew 模板(crew/) @@ -21,26 +28,26 @@ wiseflow 开源社区版本自带的官方 addon,在 wiseflow 默认全局技 | Crew / 技能层 | 核心能力 | |---|---| -| **selfmedia-operator** | 日常灵感记录、素材搜集;选题研究→图文输出、草稿扩写→完整文章;短视频 AI 生成;支持掘金 / Medium / 知乎 / 头条 / Twitter / Instagram / TikTok / YouTube 发布;可自主调用 designer 完成配图 | -| **business-developer** | 人脉关系网络优化与拓展建议;批量邮件撰写与发送;融资 / 商务演示文稿生成;社交图谱关键节点分析与排序 | -| **sales-cs** | 首问接待、售前咨询、销售引导一体化;客户数据库自动维护(业务状态、来源渠道、意向追踪);内置收款发起、体验邀请、遇到客户说晚些聊等情况自动后续跟进;智能判断升级人工 | +| **selfmedia-operator** | 日常灵感记录、素材搜集;选题研究→图文输出、草稿扩写→完整文章;短视频 AI 生成;多平台发布;可自主调用 designer 完成配图 | +| **business-developer** | 商业情报采集、潜客挖掘、评论区互动拓展;人脉网络优化、邮件触达;融资/商务演示文稿生成 | +| **ir** | 商业模式打磨与定期复盘;融资路演材料制作;投资人发掘与跟进;项目申报;软件著作权登记 | +| **sales-cs** | 首问接待、售前咨询、销售引导一体化;客户数据库自动维护;内置收款发起、体验邀请、自动后续跟进;智能判断升级人工 | | **designer** | 文生图、图片修改;可被其他 crew 通过 `sessions_spawn` 调用 | --- -#### sales-cs — 销售型客服 +#### ir — 投资人关系专员 -**类型**:对外(external)`T0` 权限 +**类型**:对内(internal)`T1` 权限 -以**促进成交**为核心目标,而非单纯被动答疑。 +老板的商业打磨合伙人和融资执行手——核心价值是长期积累 + 定期复盘迭代商业模式,在此基础上执行融资相关事务。 -- 首问接待、售前咨询、销售引导一体化 -- 话术原则:先承接 → 再判断 → 给结论 → 推下一步 -- 自动维护客户数据库(业务状态、来源渠道、意向追踪) -- 内置收款发起(payment_send)、体验邀请(exp_invite)、主动触达(proactive-send)等专属技能 -- 超过 20 轮对话后自动升级人工,并记录用户不满到 feedback/ +三大工作块: +- **商业模式打磨**:记录创业思路/经验教训,定期 council 复盘,梳理迭代商业模式,维护 BP,制作融资路演材料 +- **项目申报**:上网填报创业比赛/项目申请/软件著作权登记,防重复,可定期总结 +- **投资人发掘与跟进**:主动发掘和跟进投资人,国内场景主要在社交平台操作 -专属技能:`customer-db` / `demo_send` / `exp_invite` / `payment_send` / `proactive-send` +专属技能:`ir-record` / `investor-hunting` / `investor-materials` / `investor-outreach` / `market-research` / `swcr-register` --- @@ -54,52 +61,72 @@ wiseflow 开源社区版本自带的官方 addon,在 wiseflow 默认全局技 - 配图优先级:用户上传 > 网络免版权 > AI 生成(siliconflow-img-gen)> 历史素材复用 - 视频制作:通过 `t2video` 技能完成短视频制作(TTS 语音合成 + AI 视频片段生成 + 素材组装) - 素材统一归档到 `campaign_assets/`,维护 index.md 方便复用 -- 支持自动发布到知乎/头条/掘金/Medium(wenyan-publisher)、Twitter、Instagram、TikTok、YouTube +- 支持自动发布到微信公众号、知乎、头条、掘金、Medium、Twitter/X、Instagram、TikTok、YouTube、抖音、Facebook、Threads、Pinterest 等平台 -专属技能:`wenyan-publisher` / `twitter-post` / `instagram-post` / `tiktok-post` / `youtube-upload` / `t2video` +专属技能:`wenyan-publisher` / `twitter-post` / `instagram-publish` / `tiktok-publish` / `youtube-publish` / `douyin-publish` / `toutiao-publish` / `juejin-publish` / `facebook-publish` / `threads-publish` / `pinterest-publish` / `xhs-content-ops` / `t2video` / `highlight-clipper` / `published-track` --- -#### designer — 设计师 +#### business-developer — 商务拓展 **类型**:对内(internal)`T2` 权限 -专注视觉创意设计,结合 AI 生图能力提供配图、海报、品牌素材生成服务。 +专注商务拓展场景,具备其他 crew 不具备的商务专属技能组合。 -- 调用 `siliconflow-img-gen` 进行文生图和图片修改 -- 配合 selfmedia-operator、business-developer 等 crew 完成视觉需求 -- 可被其他 crew 通过 `sessions_spawn` 调用(allowAgents 中配置) +- **情报采集**:`intel-gathering` 定时监控信源提取商业情报;`info-record` 情报去重与查询 +- **潜客挖掘**:`lead-hunting` 自媒体平台按策略探索潜在客户;`bd-record` 追踪数据库去重 +- **互动拓展**:`comment-engagement` 在评论区以留言/回复/私信方式拓展潜在客户 +- **人脉触达**:`connections-optimizer` 人脉网络优化;`email-ops` 批量邮件撰写与发送 +- **演示文稿**:`pitch-deck` HTML 演示文稿;`ppt-maker` PPTX 演示文稿 +- **社交分析**:`social-graph-ranker` 社交图谱关键节点排序 + +专属技能:`intel-gathering` / `info-record` / `lead-hunting` / `bd-record` / `comment-engagement` / `connections-optimizer` / `email-ops` / `pitch-deck` / `ppt-maker` / `social-graph-ranker` --- -#### business-developer — 商务拓展 +#### sales-cs — 销售型客服 + +**类型**:对外(external)`T0` 权限 + +以**促进成交**为核心目标,而非单纯被动答疑。 + +- 首问接待、售前咨询、销售引导一体化 +- 话术原则:先承接 → 再判断 → 给结论 → 推下一步 +- 自动维护客户数据库(业务状态、来源渠道、意向追踪) +- 内置收款发起(payment_send)、体验邀请(exp_invite)、主动触达(proactive-send)等专属技能 +- 超过 20 轮对话后自动升级人工,并记录用户不满到 feedback/ + +专属技能:`customer-db` / `demo_send` / `exp_invite` / `payment_send` / `proactive-send` + +--- + +#### designer — 设计师 **类型**:对内(internal)`T2` 权限 -专注商务拓展场景,具备其他 crew 不具备的商务专属技能组合。 +专注视觉创意设计,结合 AI 生图能力提供配图、海报、品牌素材生成服务。 -- `connections-optimizer`:人脉关系网络优化与拓展建议 -- `email-ops`:批量邮件撰写与发送(SMTP 配置) -- `pitch-deck`:融资 / 商务演示文稿生成 -- `social-graph-ranker`:社交图谱关键节点分析与排序 +- 调用 `siliconflow-img-gen` 进行文生图和图片修改 +- 配合 selfmedia-operator、business-developer 等 crew 完成视觉需求 +- 可被其他 crew 通过 `sessions_spawn` 调用(allowAgents 中配置) -专属技能:`connections-optimizer` / `email-ops` / `pitch-deck` / `social-graph-ranker` +专属技能:`design-system-picker` / `init-workspace` --- --- -## 四 Crew 协同:自动化获客全链路管线 +## 五 Crew 协同:自动化获客全链路管线 -以上四个 Crew 模板并非孤立工具,它们共同构成了一套**端到端的自动化获客闭环**——从内容种草、主动拓客,到客服转化,全链路无人值守自动运转: +以上五个 Crew 模板并非孤立工具,它们共同构成了一套**端到端的自动化获客闭环**——从内容种草、主动拓客,到客服转化,全链路无人值守自动运转: ``` ┌─────────────────────┐ ┌──────────────────────┐ │ selfmedia-operator │ │ business-developer │ │ [内容种草 · 引流] │ │ [主动触达 · 拓客] │ │ │ │ │ - │ 多平台持续发布内容 │ │ 人脉分析 & 邮件冷触达│ - │ 吸引潜在用户自然关注│ │ 锁定并主动找到目标客 │ + │ 多平台持续发布内容 │ │ 情报采集 & 潜客挖掘 │ + │ 吸引潜在用户自然关注│ │ 评论区互动 & 邮件触达│ └────────┬────────────┘ └──────────┬───────────┘ │ ↑ │ ↑ │ 按需 spawn 按需 spawn @@ -120,12 +147,22 @@ wiseflow 开源社区版本自带的官方 addon,在 wiseflow 默认全局技 │以成交为目标 │ │ 收款 & 追踪 │ └──────────────┘ + + ┌──────────────────────┐ + │ ir │ + │ [融资 · 著作权] │ + │ │ + │ 商业模式打磨 & 复盘 │ + │ 投资人发掘与跟进 │ + │ 项目申报 & 软著登记 │ + └──────────────────────┘ ``` | 阶段 | Crew | 核心职责 | |------|------|----------| -| 内容引流 | `selfmedia-operator` | 在微信公众号、知乎、头条、Twitter、Instagram、TikTok、YouTube 等平台持续输出内容,吸引自然流量进入 | -| 主动拓客 | `business-developer` | 人脉关系网络分析、批量邮件冷触达、商务 pitch 生成,主动锁定并触达目标客户 | +| 内容引流 | `selfmedia-operator` | 在微信公众号、知乎、头条、掘金、Twitter/X、Instagram、TikTok、YouTube 等平台持续输出内容,吸引自然流量进入 | +| 主动拓客 | `business-developer` | 商业情报采集、潜客挖掘、评论区互动拓展、批量邮件冷触达、商务 pitch 生成 | +| 融资支持 | `ir` | 商业模式打磨与定期复盘、投资人发掘与跟进、项目申报与软件著作权登记 | | 视觉支援 | `designer` | 按需被 selfmedia-operator 或 business-developer 通过 `sessions_spawn` 唤起,提供配图、海报、品牌素材 | | 线索转化 | `sales-cs` | 7×24 小时在线接客,以促成交为核心目标,内置收款发起、意向追踪、超限自动升级人工等机制 | diff --git a/addons/officials/addon.json b/addons/officials/addon.json index 1a7a985e..04a743ed 100644 --- a/addons/officials/addon.json +++ b/addons/officials/addon.json @@ -1,7 +1,7 @@ { "name": "wiseflow officials", - "version": "0.4.1", - "description": "官方 Crew 模板(selfmedia-operator / business-developer / designer / ir / sales-cs)+ 专属全局技能(rss-reader / siliconflow-img-gen / pexels-footage / pixabay-footage / connections-optimizer / email-ops / pitch-deck / ppt-maker / social-graph-ranker / xhs-interact / xianyu-ops)", + "version": "0.5.0", + "description": "官方 Crew 模板(selfmedia-operator / business-developer / designer / ir / sales-cs)+ 专属全局技能(rss-reader / siliconflow-img-gen / pexels-footage / pixabay-footage / council / connections-optimizer / email-ops / pitch-deck / ppt-maker / social-graph-ranker / web-form-fill / xhs-interact / xianyu-ops)", "openclaw_version": "2026.5.28", "openclaw_commit": "e93216080aa1f425d3ab127014603eba8e365b2d", "auto-activate": false, diff --git a/addons/officials/crew/ir/AGENTS.md b/addons/officials/crew/ir/AGENTS.md index 2c9ece16..1e4623ea 100644 --- a/addons/officials/crew/ir/AGENTS.md +++ b/addons/officials/crew/ir/AGENTS.md @@ -13,7 +13,7 @@ | 关键词 | 工作块 | |--------|--------| | 商业模式、复盘、BP、路演材料、Pitch Deck、融资材料、商业梳理、经验教训、想法、思路 | **商业模式打磨** | -| 申报、比赛、创业大赛、项目申请、补贴、政策申报 | **项目申报** | +| 申报、比赛、创业大赛、项目申请、补贴、政策申报、软著、软件著作权、著作权登记 | **项目申报** | | 找投资人、VC、投资机构、投资人搜索、触达、联系投资人、进展、跟进、尽调、DD、关系维护 | **投资人发掘与跟进** | --- @@ -118,6 +118,16 @@ BP 和 Pitch Deck 是商业模式的表达形式,不仅仅是融资工具。 - 财务数据 / 团队信息 - 附件材料 +### Phase 4.5: 软件著作权登记 + +当用户需要申请软件著作权时,调用 **swcr-register** 技能: + +1. 收集软件信息(全称、简称、版本号、开发日期、著作权人等) +2. 指定代码仓 URL 或本地目录 +3. 生成三份材料:源程序文档(DOCX)、操作手册(DOCX)、填报信息 Markdown +4. 用户确认材料无误后,可选辅助在线填报(web-form-fill) +5. 用 ir-record 记录申报状态 + ### Phase 5: 在线填报 调用 **web-form-fill** 技能执行完整填报流程: diff --git a/addons/officials/crew/ir/skills/swcr-register/SKILL.md b/addons/officials/crew/ir/skills/swcr-register/SKILL.md new file mode 100644 index 00000000..18cfe4e4 --- /dev/null +++ b/addons/officials/crew/ir/skills/swcr-register/SKILL.md @@ -0,0 +1,213 @@ +--- +name: swcr-register +description: > + 软件著作权登记全流程:从代码仓/目录生成程序鉴别材料(源程序文档)、 + 软件操作手册、申请填报信息 Markdown,并可辅助在线填报。 + 当用户需要申请软件著作权、生成软著材料时触发。 +metadata: + openclaw: + emoji: 📜 +--- + +# 软件著作权登记 + +一键生成中国计算机软件著作权登记所需的全部材料,并可辅助完成在线填报。 + +**依赖技能**:`web-form-fill`(在线填报时使用) + +--- + +## 触发条件 + +- 申请软件著作权 / 软著 +- 生成程序鉴别材料 / 源程序文档 +- 生成软件操作手册 +- 需要软著登记填报信息 + +--- + +## 核心工作流 + +``` +1. 收集信息 → 2. 生成材料 → 3. 用户确认 → 4. 在线填报(可选) +``` + +### Step 1:收集信息 + +向用户确认以下信息(已有明确答案的跳过): + +| 信息项 | 说明 | 示例 | +|--------|------|------| +| 代码来源 | GitHub URL 或本地目录路径 | `https://github.com/user/repo` 或 `/path/to/project` | +| 软件全称 | 完整软件名称,需与简称不同 | `智能数据分析平台软件` | +| 软件简称 | 缩写或简称 | `智数平台` | +| 版本号 | 格式 V1.0 或 1.0 | `V1.0` | +| 开发完成日期 | 格式 YYYY-MM-DD | `2026-05-20` | +| 首次发表日期 | 格式 YYYY-MM-DD,未发表填"未发表" | `2026-06-01` | +| 开发方式 | 独立开发 / 合作开发 | `独立开发` | +| 著作权人 | 著作权人姓名/名称 | `张三` | +| 作者 | 开发者姓名(独立开发时与著作权人一致) | `张三` | + +**可选信息**(有默认值,用户可覆盖): + +| 信息项 | 默认值 | 说明 | +|--------|--------|------| +| 源代码后缀 | 自动检测 | 不指定时脚本自动识别 | +| 注释字符 | 自动检测 | 不指定时脚本自动识别 | +| 排除目录 | `node_modules, .git, __pycache__, venv, dist, build` | 常见非源码目录 | +| 输出目录 | 当前工作区 | 生成的文件存放位置 | + +**合作开发额外信息**: + +如果开发方式为合作开发,还需收集: +- 其他作者姓名 +- 其他著作权人(必须在登记网站注册并完成实名认证) +- 合作开发协议文件路径(如已有) + +### Step 2:准备代码 + +- 如果用户提供的是 GitHub URL:`git clone ` 到临时目录 +- 如果是本地目录:直接使用 +- 自动分析代码结构,识别主要编程语言和文件扩展名 + +### Step 3:生成材料 + +依次执行三个脚本,生成三份材料: + +#### 3-A. 程序鉴别材料(源程序文档) + +```bash +python ./skills/swcr-register/scripts/generate_code_doc.py \ + --title "<软件全称>" \ + --version "<版本号>" \ + --source-dir "<代码目录>" \ + --output "<输出目录>/<软件简称>_源程序.docx" \ + [--exts py js ts] \ + [--comment-chars "#" "//"] \ + [--excludes "node_modules" ".git"] \ + [--max-front-pages 30] \ + [--max-back-pages 30] +``` + +生成规则: +- 每页 50 行有效代码(非空行、非注释行) +- 前 30 页 + 后 30 页,中间省略页 +- 源程序量 > 3000 行时,文档必须为 61 页 +- 源程序量 ≤ 3000 行时,文档可少于 61 页 +- 页眉:软件名称 + 版本号(左)+ 页码(右) +- 代码字体:Courier New 8pt +- 中文辅助字体:SimSun + +#### 3-B. 软件操作手册 + +```bash +python ./skills/swcr-register/scripts/generate_manual.py \ + --title "<软件全称>" \ + --version "<版本号>" \ + --readme "" \ + --output "<输出目录>/<软件简称>_操作手册.docx" +``` + +生成规则: +- 从 README.md 转换为格式化 DOCX +- 页眉:软件名称 + 版本号 +- 标题、正文、代码块、列表等正确排版 +- 中文字体:SimHei;代码字体:Courier New + +#### 3-C. 申请填报信息 Markdown + +```bash +python ./skills/swcr-register/scripts/generate_form_info.py \ + --title "<软件全称>" \ + --short-name "<软件简称>" \ + --version "<版本号>" \ + --source-dir "<代码目录>" \ + --completion-date "<开发完成日期>" \ + --publish-date "<首次发表日期>" \ + --dev-method "<开发方式>" \ + --author "<作者>" \ + --copyright-holder "<著作权人>" \ + --output "<输出目录>/<软件简称>_填报信息.md" \ + [--co-authors "作者2" "作者3"] \ + [--co-holders "著作权人2" "著作权人3"] +``` + +生成内容包含: +- 软件全称与简称(确保不同) +- 版本号 +- 开发完成日期与首次发表日期 +- 开发方式与著作权人信息 +- **软件主要功能**(100 字以上,从 README 提取) +- **软件技术特点**(50 字以上,从代码结构分析) +- 源程序量(行数) +- 源程序文档页数 +- 需上传的文件清单及对应字段 +- 线下邮寄材料清单 + +### Step 4:用户确认 + +**必须等用户确认后再进行下一步。** + +向用户展示: +1. 三份生成文件的路径 +2. 填报信息 Markdown 的关键内容摘要 +3. 询问: + - "请检查生成的材料是否有问题,需要修改请告知。" + - "确认无误后,是否需要我辅助进行在线填报?(是/否)" + +### Step 5:在线填报(用户确认后) + +仅在用户明确要求辅助填报时执行。 + +1. 打开 https://register.ccopyright.com.cn/registration.html#/registerSoft +2. 选择 **R11** → **计算机软件著作权登记申请** → 点击 **立即登记** +3. **提醒用户登录账号**(如未注册需先注册 + 实名认证,认证需 1-3 天) +4. 用户确认登录完成后,调用 **web-form-fill** 技能: + - 选择"我是申请人" + - 填写软件信息(全称、简称、版本号) + - 填写开发信息(开发方式、完成日期、发表日期、作者、著作权人) + - 填写软件功能与特点(主要功能 100+ 字、技术特点 50+ 字) + - 上传程序鉴别材料(源程序文档 .docx) + - 上传文档鉴别材料(操作手册 .docx) + - 信息确认页填写 + - 选择邮寄方式 → 挂号信 → 填写收信地址 +5. **web-form-fill 的提交前确认步骤**:所有内容填完后,截图让用户确认,**禁止自动提交** + +--- + +## 填报注意事项 + +| 事项 | 要求 | +|------|------| +| 软件全称与简称 | **必须不同** | +| 版本号 | V1.0 或 1.0 | +| 主要功能 | **100 字以上** | +| 技术特点 | **50 字以上** | +| 合作开发 | 需上传合作开发协议,其他著作权人须在网站注册并实名认证 | +| 证书副本数量 | 有几个其他著作权人就填几 | +| 源程序量 > 3000 行 | 源程序文档必须 61 页,每页 50 行 | +| 源程序量 ≤ 3000 行 | 源程序文档可少于 61 页 | +| 身份证复印件 | 一页即可 | +| 打印要求 | 所有材料**单面打印** | +| 证书领取 | 选择挂号信邮寄 | + +--- + +## 线下邮寄材料清单 + +在线填报提交后,需打印以下材料邮寄: + +1. 软件著作权登记申请表(网站自动生成,下载打印) +2. 申请人身份证明(身份证复印件,一页) +3. 程序鉴别材料(源程序文档打印件) +4. 文档鉴别材料(操作手册打印件) +5. 合作开发协议(如适用) + +--- + +## 与其他技能协作 + +| 场景 | 配合技能 | +|------|---------| +| 在线填报表单 | `web-form-fill` | +| 记录申报状态 | `ir-record` | diff --git a/addons/officials/crew/ir/skills/swcr-register/scripts/generate_code_doc.py b/addons/officials/crew/ir/skills/swcr-register/scripts/generate_code_doc.py new file mode 100644 index 00000000..bd48563e --- /dev/null +++ b/addons/officials/crew/ir/skills/swcr-register/scripts/generate_code_doc.py @@ -0,0 +1,436 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Generate software copyright registration source code document (DOCX). + +Produces a formatted Word document containing source code that meets +Chinese software copyright authority requirements: +- Each page contains 50 effective lines of code (non-blank, non-comment) +- Front 30 pages + back 30 pages with an ellipsis page in between +- Header: software name + version (left) + page number (right) +- Code font: Courier New 8pt; Chinese auxiliary font: SimSun + +Usage: + python generate_code_doc.py \ + --title "智能数据分析平台软件" \ + --version "V1.0" \ + --source-dir /path/to/code \ + --output output.docx +""" + +import argparse +import codecs +import logging +import sys +from os import scandir +from os.path import abspath, relpath +from typing import List + +try: + import chardet + CHARDET_AVAILABLE = True +except ImportError: + CHARDET_AVAILABLE = False + +try: + from docx import Document + from docx.shared import Pt, Inches + from docx.enum.text import WD_ALIGN_PARAGRAPH + from docx.oxml.ns import qn + DOCX_AVAILABLE = True +except ImportError: + DOCX_AVAILABLE = False + +logger = logging.getLogger(__name__) + +DEFAULT_EXTS = [ + "c", "h", "py", "js", "ts", "java", "cpp", "hpp", + "go", "rs", "rb", "php", "cs", "swift", "kt", "scala", + "jsx", "tsx", "vue", "svelte", +] +DEFAULT_COMMENT_CHARS = ["/*", "* ", "*/", "//", "#"] +DEFAULT_EXCLUDES = [ + "node_modules", ".git", "__pycache__", "venv", ".venv", + "dist", "build", ".next", ".nuxt", "target", "bin", + "obj", ".idea", ".vscode", "coverage", ".cache", +] + + +def detect_encoding(file_path: str) -> str: + """Detect file encoding using chardet with fallback.""" + if not CHARDET_AVAILABLE: + return "utf-8" + + with open(file_path, "rb") as fd: + raw_data = fd.read(32768) # 32KB sample is sufficient for chardet + + result = chardet.detect(raw_data) + encoding = result.get("encoding", "utf-8") + confidence = result.get("confidence", 0) + + if confidence < 0.7: + for enc in ["utf-8", "gbk", "gb2312", "big5", "latin-1"]: + try: + raw_data.decode(enc) + encoding = enc + break + except (UnicodeDecodeError, LookupError): + continue + + return encoding + + +def find_code_files( + source_dir: str, + exts: List[str], + excludes: List[str], +) -> List[str]: + """Recursively find source code files matching extensions, excluding dirs.""" + files: List[str] = [] + abs_excludes = [abspath(e) for e in excludes] + + def _should_exclude(path: str) -> bool: + abs_path = abspath(path) + return any(abs_path.startswith(ex) for ex in abs_excludes) + + def _scan(directory: str) -> None: + try: + for entry in scandir(directory): + if entry.name.startswith("."): + continue + if _should_exclude(entry.path): + continue + if entry.is_file(): + if any(entry.name.endswith(f".{ext}") for ext in exts): + files.append(abspath(entry.path)) + elif entry.is_dir(): + _scan(entry.path) + except PermissionError: + logger.warning("Permission denied: %s", directory) + + _scan(source_dir) + return files + + +def is_blank_line(line: str) -> bool: + return not bool(line.strip()) + + +def is_comment_line(line: str, comment_chars: List[str]) -> bool: + stripped = line.lstrip() + return any(stripped.startswith(cc) for cc in comment_chars) + + +def wrap_long_line(line: str, max_chars: int = 90) -> List[str]: + if len(line) <= max_chars: + return [line] + wrapped = [] + while len(line) > max_chars: + wrapped.append(line[:max_chars]) + line = line[max_chars:] + if line: + wrapped.append(line) + return wrapped + + +def collect_code_lines( + files: List[str], + comment_chars: List[str], + base_dir: str, +) -> List[str]: + """Read all source files and collect code lines.""" + all_lines: List[str] = [] + + for filepath in files: + encoding = detect_encoding(filepath) + logger.info("Processing: %s (encoding: %s)", filepath, encoding) + + try: + relative = relpath(filepath, base_dir) + except ValueError: + relative = filepath + + all_lines.append(f"# File: {relative}") + + try: + with codecs.open(filepath, "r", encoding, errors="replace") as fp: + for line in fp: + line = line.rstrip() + all_lines.extend(wrap_long_line(line, max_chars=90)) + except Exception as exc: + logger.error("Error reading %s: %s", filepath, exc) + all_lines.append(f"# Error reading file: {exc}") + + return all_lines + + +def count_effective_lines(lines: List[str], comment_chars: List[str]) -> int: + """Count non-blank, non-comment lines.""" + return sum( + 1 for line in lines + if not is_blank_line(line) and not is_comment_line(line, comment_chars) + ) + + +def _is_effective(line: str, comment_chars: List[str]) -> bool: + """Check if a line is effective (non-blank and non-comment).""" + return not is_blank_line(line) and not is_comment_line(line, comment_chars) + + +def split_into_pages( + all_lines: List[str], + comment_chars: List[str], + lines_per_page: int = 50, + max_front_pages: int = 30, + max_back_pages: int = 30, +) -> tuple: + """Split code lines into front pages and back pages. + + Each page contains lines_per_page effective lines (non-blank, non-comment). + """ + if not all_lines: + return [], [] + + front_pages: List[List[str]] = [] + back_pages: List[List[str]] = [] + + current_page: List[str] = [] + effective_count = 0 + page_count = 0 + + i = 0 + while i < len(all_lines) and page_count < max_front_pages: + line = all_lines[i] + current_page.append(line) + if _is_effective(line, comment_chars): + effective_count += 1 + if effective_count >= lines_per_page or i == len(all_lines) - 1: + front_pages.append(current_page.copy()) + logger.info( + "Front page %d: %d lines, %d effective", + page_count + 1, len(current_page), effective_count, + ) + current_page = [] + effective_count = 0 + page_count += 1 + i += 1 + + if i < len(all_lines): + remaining = all_lines[i:] + remaining_effective = count_effective_lines(remaining, comment_chars) + + if remaining_effective > max_back_pages * lines_per_page: + target = max_back_pages * lines_per_page + start_pos = len(remaining) - 1 + eff = 0 + for j in range(len(remaining) - 1, -1, -1): + if _is_effective(remaining[j], comment_chars): + eff += 1 + if eff >= target: + start_pos = j + break + back_source = remaining[start_pos:] + else: + back_source = remaining + + current_page = [] + effective_count = 0 + for line in back_source: + current_page.append(line) + if _is_effective(line, comment_chars): + effective_count += 1 + if effective_count >= lines_per_page: + back_pages.append(current_page.copy()) + current_page = [] + effective_count = 0 + if current_page: + back_pages.append(current_page) + + return front_pages, back_pages + + +def create_docx( + filename: str, + title: str, + version: str, + front_pages: List[List[str]], + back_pages: List[List[str]], +) -> None: + """Create the source code DOCX document.""" + if not DOCX_AVAILABLE: + print("Error: python-docx is required. Install with: pip install python-docx") + sys.exit(1) + + doc = Document() + + for section in doc.sections: + section.top_margin = Inches(0.8) + section.bottom_margin = Inches(0.5) + section.left_margin = Inches(0.8) + section.right_margin = Inches(0.5) + + total_front = len(front_pages) + + for page_num, page_lines in enumerate(front_pages, 1): + _add_code_page(doc, page_lines, page_num, title, version) + if page_num < total_front or back_pages: + doc.add_page_break() + + if back_pages: + _add_ellipsis_page(doc, total_front + 1, title, version) + for page_num, page_lines in enumerate(back_pages, total_front + 2): + doc.add_page_break() + _add_code_page(doc, page_lines, page_num, title, version) + + doc.save(filename) + + +def _add_code_page( + doc: Document, + lines: List[str], + page_num: int, + title: str, + version: str, +) -> None: + """Add one page of code to the document.""" + header_text = f"{title} {version}" + p = doc.add_paragraph() + run = p.add_run(header_text) + run.font.size = Pt(9) + run = p.add_run(f"\t\t\t\t\t\t\t\t\t\t{page_num}") + run.font.size = Pt(9) + p.paragraph_format.space_before = Pt(0) + p.paragraph_format.space_after = Pt(2) + p.paragraph_format.line_spacing = 1.0 + + p = doc.add_paragraph() + run = p.add_run("_" * 95) + run.font.size = Pt(8) + p.paragraph_format.space_before = Pt(0) + p.paragraph_format.space_after = Pt(4) + p.paragraph_format.line_spacing = 1.0 + + for line in lines: + p = doc.add_paragraph() + run = p.add_run(line if line.strip() else " ") + run.font.name = "Courier New" + run.font.size = Pt(8) + p.paragraph_format.space_before = Pt(0) + p.paragraph_format.space_after = Pt(0) + p.paragraph_format.line_spacing = 1.0 + r = run._element + r.rPr.rFonts.set(qn("w:eastAsia"), "SimSun") + + +def _add_ellipsis_page( + doc: Document, + page_num: int, + title: str, + version: str, +) -> None: + """Add an ellipsis page to the document.""" + header = f"{title} {version}" + p = doc.add_paragraph() + run = p.add_run(header) + run.font.size = Pt(10) + run = p.add_run(f"\t\t\t\t\t\t\t\t\t\t{page_num}") + run.font.size = Pt(10) + + p = doc.add_paragraph() + p.add_run("_" * 100) + + for _ in range(20): + doc.add_paragraph() + + p = doc.add_paragraph() + run = p.add_run("......") + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + run.font.size = Pt(24) + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Generate source code document for software copyright registration." + ) + parser.add_argument("--title", required=True, help="Software full name") + parser.add_argument("--version", default="V1.0", help="Software version") + parser.add_argument("--source-dir", required=True, help="Source code directory") + parser.add_argument("--output", required=True, help="Output DOCX file path") + parser.add_argument( + "--exts", nargs="+", default=None, + help="Source code file extensions (auto-detect if omitted)", + ) + parser.add_argument( + "--comment-chars", nargs="+", default=None, + help="Comment characters (auto-detect if omitted)", + ) + parser.add_argument( + "--excludes", nargs="+", default=DEFAULT_EXCLUDES, + help="Directories to exclude", + ) + parser.add_argument( + "--max-front-pages", type=int, default=30, + help="Max front pages (default: 30)", + ) + parser.add_argument( + "--max-back-pages", type=int, default=30, + help="Max back pages (default: 30)", + ) + parser.add_argument( + "--verbose", action="store_true", + help="Enable verbose logging", + ) + + args = parser.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + + if not DOCX_AVAILABLE: + print("Error: python-docx is required. Install with: pip install python-docx") + return 1 + + exts = args.exts if args.exts else DEFAULT_EXTS + comment_chars = args.comment_chars if args.comment_chars else DEFAULT_COMMENT_CHARS + + source_dir = abspath(args.source_dir) + + print(f"Scanning source code in: {source_dir}") + print(f"Extensions: {exts}") + print(f"Comment chars: {comment_chars}") + print(f"Excludes: {args.excludes}") + + files = find_code_files(source_dir, exts, args.excludes) + print(f"Found {len(files)} source code files") + + if not files: + print("Error: No source code files found. Check --source-dir and --exts.") + return 1 + + all_lines = collect_code_lines(files, comment_chars, source_dir) + print(f"Total lines collected: {len(all_lines)}") + + effective = count_effective_lines(all_lines, comment_chars) + print(f"Effective lines (non-blank, non-comment): {effective}") + + front_pages, back_pages = split_into_pages( + all_lines, comment_chars, + lines_per_page=50, + max_front_pages=args.max_front_pages, + max_back_pages=args.max_back_pages, + ) + print(f"Front pages: {len(front_pages)}") + print(f"Back pages: {len(back_pages)}") + total_pages = len(front_pages) + (1 if back_pages else 0) + len(back_pages) + print(f"Total pages (including ellipsis): {total_pages}") + + create_docx(args.output, args.title, args.version, front_pages, back_pages) + print(f"Source code document created: {args.output}") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/addons/officials/crew/ir/skills/swcr-register/scripts/generate_form_info.py b/addons/officials/crew/ir/skills/swcr-register/scripts/generate_form_info.py new file mode 100644 index 00000000..9fa38e36 --- /dev/null +++ b/addons/officials/crew/ir/skills/swcr-register/scripts/generate_form_info.py @@ -0,0 +1,486 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Generate form-filling information Markdown for software copyright registration. + +Analyzes the codebase and README to produce a Markdown file containing all +information needed to fill the online registration form at +https://register.ccopyright.com.cn/registration.html#/registerSoft + +The Markdown includes: +- Software name (full and short), version +- Development info (dates, method, authors, copyright holders) +- Software functions and technical features (extracted from README) +- Source code statistics (line count, page count) +- Upload file checklist +- Offline mailing material checklist + +Usage: + python generate_form_info.py \ + --title "智能数据分析平台软件" \ + --short-name "智数平台" \ + --version "V1.0" \ + --source-dir /path/to/code \ + --completion-date 2026-05-20 \ + --publish-date 2026-06-01 \ + --dev-method "独立开发" \ + --author "张三" \ + --copyright-holder "张三" \ + --output form_info.md +""" + +import argparse +import logging +import os +import re +import sys +from os import scandir +from os.path import abspath +from typing import List + +logger = logging.getLogger(__name__) + +DEFAULT_EXTS = [ + "c", "h", "py", "js", "ts", "java", "cpp", "hpp", + "go", "rs", "rb", "php", "cs", "swift", "kt", "scala", + "jsx", "tsx", "vue", "svelte", +] +DEFAULT_COMMENT_CHARS = ["/*", "* ", "*/", "//", "#"] +DEFAULT_EXCLUDES = [ + "node_modules", ".git", "__pycache__", "venv", ".venv", + "dist", "build", ".next", ".nuxt", "target", "bin", + "obj", ".idea", ".vscode", "coverage", ".cache", +] + + +def count_source_lines( + source_dir: str, + exts: List[str], + excludes: List[str], + comment_chars: List[str], +) -> dict: + """Count total and effective source code lines.""" + total_lines = 0 + effective_lines = 0 + file_count = 0 + abs_excludes = [abspath(e) for e in excludes] + + def _should_exclude(path: str) -> bool: + abs_path = abspath(path) + return any(abs_path.startswith(ex) for ex in abs_excludes) + + def _scan(directory: str) -> None: + nonlocal total_lines, effective_lines, file_count + try: + for entry in scandir(directory): + if entry.name.startswith("."): + continue + if _should_exclude(entry.path): + continue + if entry.is_file(): + if any(entry.name.endswith(f".{ext}") for ext in exts): + try: + with open(entry.path, "r", encoding="utf-8", errors="replace") as f: + for line in f: + total_lines += 1 + stripped = line.strip() + if stripped and not any( + stripped.startswith(cc) for cc in comment_chars + ): + effective_lines += 1 + file_count += 1 + except Exception as exc: + logger.warning("Error reading file %s: %s", entry.path, exc) + elif entry.is_dir(): + _scan(entry.path) + except PermissionError: + logger.warning("Permission denied: %s", directory) + + _scan(source_dir) + return { + "total_lines": total_lines, + "effective_lines": effective_lines, + "file_count": file_count, + } + + +def calculate_pages(effective_lines: int, lines_per_page: int = 50) -> dict: + """Calculate source code document page count.""" + if effective_lines <= 0: + return {"total_pages": 0, "front_pages": 0, "back_pages": 0} + + total_pages_needed = (effective_lines + lines_per_page - 1) // lines_per_page + + if total_pages_needed <= 60: + return { + "total_pages": total_pages_needed, + "front_pages": total_pages_needed, + "back_pages": 0, + } + + front = 30 + back = 30 + total = front + 1 + back # +1 for ellipsis page + return { + "total_pages": total, + "front_pages": front, + "back_pages": back, + } + + +def detect_languages(source_dir: str, excludes: List[str]) -> List[str]: + """Detect primary programming languages in the codebase.""" + lang_map = { + "py": "Python", "js": "JavaScript", "ts": "TypeScript", + "java": "Java", "c": "C", "h": "C", "cpp": "C++", "hpp": "C++", + "go": "Go", "rs": "Rust", "rb": "Ruby", "php": "PHP", + "cs": "C#", "swift": "Swift", "kt": "Kotlin", "scala": "Scala", + "jsx": "React JSX", "tsx": "React TSX", "vue": "Vue", "svelte": "Svelte", + } + ext_counts: dict = {} + abs_excludes = [abspath(e) for e in excludes] + + def _should_exclude(path: str) -> bool: + abs_path = abspath(path) + return any(abs_path.startswith(ex) for ex in abs_excludes) + + def _scan(directory: str) -> None: + try: + for entry in scandir(directory): + if entry.name.startswith("."): + continue + if _should_exclude(entry.path): + continue + if entry.is_file(): + ext = entry.name.rsplit(".", 1)[-1] if "." in entry.name else "" + if ext in lang_map: + ext_counts[ext] = ext_counts.get(ext, 0) + 1 + elif entry.is_dir(): + _scan(entry.path) + except PermissionError: + logger.warning("Permission denied: %s", directory) + + _scan(source_dir) + + sorted_exts = sorted(ext_counts.keys(), key=lambda x: ext_counts[x], reverse=True) + languages = [lang_map[ext] for ext in sorted_exts if ext in lang_map] + return languages[:5] # top 5 languages + + +def extract_functions_from_readme(readme_path: str) -> dict: + """Extract software functions and features from README.""" + result = { + "main_functions": "", + "tech_features": "", + "description": "", + } + + if not readme_path or not os.path.isfile(readme_path): + return result + + try: + with open(readme_path, "r", encoding="utf-8", errors="replace") as f: + content = f.read() + except Exception: + return result + + # Extract description (first paragraph after title) + lines = content.split("\n") + desc_parts: List[str] = [] + past_title = False + for line in lines: + stripped = line.strip() + if stripped.startswith("# "): + past_title = True + continue + if past_title and stripped and not stripped.startswith("#") and not stripped.startswith("!["): + # Skip table of contents lines, horizontal rules, and short labels + if re.match(r"^[\s\-=*]+$", stripped): + continue + if len(stripped) < 5 and not any(c in stripped for c in ",。、;"): + continue + desc_parts.append(stripped) + if len(desc_parts) >= 3: + break + if past_title and not stripped and desc_parts: + break + + result["description"] = " ".join(desc_parts) + + # Extract "功能" or "Features" section + in_features = False + feature_parts: List[str] = [] + for line in lines: + stripped = line.strip() + if re.match(r"^#+\s*(功能|特性|feature|function)", stripped, re.IGNORECASE): + in_features = True + continue + if in_features: + if stripped.startswith("#"): + break + if stripped and not stripped.startswith("```"): + feature_parts.append(stripped) + + result["main_functions"] = " ".join(feature_parts[:10]) + + # Extract "技术" or "Tech" section + in_tech = False + tech_parts: List[str] = [] + for line in lines: + stripped = line.strip() + if re.match(r"^#+\s*(技术|架构|tech|arch|stack)", stripped, re.IGNORECASE): + in_tech = True + continue + if in_tech: + if stripped.startswith("#"): + break + if stripped and not stripped.startswith("```"): + tech_parts.append(stripped) + + result["tech_features"] = " ".join(tech_parts[:5]) + + return result + + +def generate_form_markdown( + title: str, + short_name: str, + version: str, + source_dir: str, + completion_date: str, + publish_date: str, + dev_method: str, + author: str, + copyright_holder: str, + co_authors: List[str], + co_holders: List[str], + stats: dict, + pages: dict, + languages: List[str], + readme_info: dict, +) -> str: + """Generate the form-filling information Markdown.""" + is_coop = dev_method == "合作开发" + all_authors = [author] + co_authors + all_holders = [copyright_holder] + co_holders + + # Generate main functions text (ensure 100+ chars) + main_functions = readme_info.get("main_functions", "") + desc = readme_info.get("description", "") + if len(main_functions) < 100 and desc: + main_functions = f"{main_functions} {desc}".strip() + if len(main_functions) < 100: + lang_str = "、".join(languages) if languages else "多种编程语言" + main_functions = ( + f"{main_functions} 本软件基于{lang_str}开发," + "提供完整的数据处理、分析和管理功能," + "支持多种数据源的接入和处理," + "具备良好的扩展性、稳定性和易用性," + "可满足不同场景下的业务需求。" + ).strip() + + # Generate tech features text (ensure 50+ chars) + tech_features = readme_info.get("tech_features", "") + if len(tech_features) < 50: + lang_str = "、".join(languages) if languages else "多种编程语言" + tech_features = ( + f"{tech_features} 本软件基于{lang_str}开发," + "采用模块化架构设计," + "具有良好的可维护性和可扩展性," + "支持跨平台部署和运行。" + ).strip() + + # Source code document page info + effective = stats.get("effective_lines", 0) + if effective > 3000: + source_doc_pages = 61 + source_doc_note = "源程序量 > 3000 行,文档必须为 61 页" + else: + source_doc_pages = pages.get("total_pages", 0) + source_doc_note = f"源程序量 ≤ 3000 行,文档 {source_doc_pages} 页" + + lines = [ + f"# 软件著作权登记 — 填报信息", + "", + f"## 软件基本信息", + "", + f"| 字段 | 值 |", + f"|------|-----|", + f"| 软件全称 | {title} |", + f"| 软件简称 | {short_name} |", + f"| 版本号 | {version} |", + f"| 开发完成日期 | {completion_date} |", + f"| 首次发表日期 | {publish_date} |", + f"| 开发方式 | {dev_method} |", + "", + f"## 著作权人信息", + "", + f"| 字段 | 值 |", + f"|------|-----|", + f"| 著作权人 | {'、'.join(all_holders)} |", + f"| 作者 | {'、'.join(all_authors)} |", + ] + + if is_coop: + lines += [ + f"| 合作开发 | 是 |", + f"| 证书副本数量 | {len(co_holders)} |", + "", + "### 合作开发注意事项", + "", + "- 需上传**合作开发协议**", + "- 其他著作权人必须在登记网站注册并完成实名认证", + "- 证书副本数量 = 其他著作权人数量", + ] + else: + lines += [ + f"| 合作开发 | 否 |", + f"| 证书副本数量 | 0 |", + ] + + lines += [ + "", + f"## 软件功能与特点", + "", + f"### 主要功能({len(main_functions)} 字)", + "", + main_functions, + "", + f"### 技术特点({len(tech_features)} 字)", + "", + tech_features, + "", + f"## 源程序统计", + "", + f"| 项目 | 值 |", + f"|------|-----|", + f"| 源代码文件数 | {stats.get('file_count', 0)} |", + f"| 总行数 | {stats.get('total_lines', 0)} |", + f"| 有效行数(非空非注释) | {effective} |", + f"| 源程序文档页数 | {source_doc_pages} |", + f"| 说明 | {source_doc_note} |", + "", + f"## 上传文件清单", + "", + f"| 表单字段 | 文件 | 格式 |", + f"|---------|------|------|", + f"| 程序鉴别材料 | {short_name}_源程序.docx | DOCX |", + f"| 文档鉴别材料 | {short_name}_操作手册.docx | DOCX |", + ] + + if is_coop: + lines += [ + f"| 合作开发协议 | 合作开发协议.pdf | PDF |", + ] + + lines += [ + "", + f"## 线下邮寄材料清单", + "", + "在线填报提交后,需**单面打印**以下材料邮寄:", + "", + "1. 软件著作权登记申请表(网站自动生成,下载打印)", + "2. 申请人身份证明(身份证复印件,**一页即可**)", + "3. 程序鉴别材料(源程序文档打印件)", + "4. 文档鉴别材料(操作手册打印件)", + ] + + if is_coop: + lines += ["5. 合作开发协议"] + + lines += [ + "", + "## 填报流程提示", + "", + "1. 打开 https://register.ccopyright.com.cn/registration.html#/registerSoft", + "2. 选择 **R11** → **计算机软件著作权登记申请** → 点击 **立即登记**", + "3. 登录账号(未注册需先注册 + 实名认证,认证需 1-3 天)", + "4. 选择 **我是申请人**", + "5. 填写软件信息(全称、简称、版本号)", + "6. 填写开发信息(开发方式、完成日期、发表日期、作者、著作权人)", + "7. 填写软件功能与特点(主要功能 100+ 字、技术特点 50+ 字)", + "8. 上传程序鉴别材料和文档鉴别材料", + "9. 信息确认页:填写身份证复印件页数(1 页),其他自动填充", + "10. 选择邮寄 → 挂号信 → 填写收信地址 → 保存并提交申请", + "", + "### 关键提醒", + "", + "- 软件全称与简称**必须不同**", + "- 主要功能**100 字以上**,技术特点**50 字以上**", + "- 所有材料**单面打印**", + "- 证书领取选择**挂号信**", + ] + + return "\n".join(lines) + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Generate form-filling info Markdown for software copyright registration." + ) + parser.add_argument("--title", required=True, help="Software full name") + parser.add_argument("--short-name", required=True, help="Software short name") + parser.add_argument("--version", default="V1.0", help="Software version") + parser.add_argument("--source-dir", required=True, help="Source code directory") + parser.add_argument("--completion-date", required=True, help="Development completion date (YYYY-MM-DD)") + parser.add_argument("--publish-date", required=True, help="First publication date (YYYY-MM-DD) or '未发表'") + parser.add_argument("--dev-method", required=True, choices=["独立开发", "合作开发"], help="Development method") + parser.add_argument("--author", required=True, help="Primary author name") + parser.add_argument("--copyright-holder", required=True, help="Copyright holder name") + parser.add_argument("--co-authors", nargs="*", default=[], help="Co-author names") + parser.add_argument("--co-holders", nargs="*", default=[], help="Co-copyright holder names") + parser.add_argument("--readme", default=None, help="Path to README.md for extracting features") + parser.add_argument("--output", required=True, help="Output Markdown file path") + parser.add_argument( + "--excludes", nargs="+", default=DEFAULT_EXCLUDES, + help="Directories to exclude from line count", + ) + + args = parser.parse_args() + + if args.title == args.short_name: + print("Error: 软件全称和简称不能相同!") + return 1 + + source_dir = abspath(args.source_dir) + + print(f"Analyzing source code in: {source_dir}") + + stats = count_source_lines(source_dir, DEFAULT_EXTS, args.excludes, DEFAULT_COMMENT_CHARS) + print(f"Files: {stats['file_count']}, Total lines: {stats['total_lines']}, Effective lines: {stats['effective_lines']}") + + pages = calculate_pages(stats["effective_lines"]) + print(f"Source doc pages: {pages['total_pages']}") + + languages = detect_languages(source_dir, args.excludes) + print(f"Languages: {', '.join(languages)}") + + readme_info = extract_functions_from_readme(args.readme) if args.readme else {} + + markdown = generate_form_markdown( + title=args.title, + short_name=args.short_name, + version=args.version, + source_dir=source_dir, + completion_date=args.completion_date, + publish_date=args.publish_date, + dev_method=args.dev_method, + author=args.author, + copyright_holder=args.copyright_holder, + co_authors=args.co_authors, + co_holders=args.co_holders, + stats=stats, + pages=pages, + languages=languages, + readme_info=readme_info, + ) + + with open(args.output, "w", encoding="utf-8") as f: + f.write(markdown) + + print(f"Form info Markdown created: {args.output}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/addons/officials/crew/ir/skills/swcr-register/scripts/generate_manual.py b/addons/officials/crew/ir/skills/swcr-register/scripts/generate_manual.py new file mode 100644 index 00000000..45669dc8 --- /dev/null +++ b/addons/officials/crew/ir/skills/swcr-register/scripts/generate_manual.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Generate software operation manual (DOCX) from README. + +Converts a README.md file into a formatted Word document suitable for +software copyright registration "文档鉴别材料" (Document Identification Material). + +Features: +- Proper heading hierarchy (H0/H1/H2) +- Code blocks with monospace font +- Bullet lists +- Tables (basic support) +- Header: software name + version +- Chinese font: SimHei; Code font: Courier New + +Usage: + python generate_manual.py \ + --title "智能数据分析平台软件" \ + --version "V1.0" \ + --readme /path/to/README.md \ + --output manual.docx +""" + +import argparse +import re +import sys +from typing import List, Tuple + +try: + from docx import Document + from docx.shared import Pt, Inches, RGBColor + from docx.enum.text import WD_ALIGN_PARAGRAPH + from docx.oxml.ns import qn + DOCX_AVAILABLE = True +except ImportError: + DOCX_AVAILABLE = False + + +def parse_markdown(content: str) -> List[Tuple[str, str]]: + """Parse markdown into a list of (type, content) tokens. + + Token types: + h0, h1, h2, h3 - headings + code_start, code_end - code block delimiters + code_line - line inside code block + list_item - bullet list item + table_row - table row (pipe-separated) + table_separator - table separator line (---|---) + paragraph - regular text + blank - empty line + hr - horizontal rule + """ + tokens: List[Tuple[str, str]] = [] + in_code_block = False + + for line in content.split("\n"): + if line.strip().startswith("```"): + if in_code_block: + tokens.append(("code_end", "")) + in_code_block = False + else: + lang = line.strip()[3:].strip() + tokens.append(("code_start", lang)) + in_code_block = True + continue + + if in_code_block: + tokens.append(("code_line", line)) + continue + + stripped = line.strip() + + if not stripped: + tokens.append(("blank", "")) + elif stripped == "---" or stripped == "***" or stripped == "___": + tokens.append(("hr", "")) + elif line.startswith("# "): + tokens.append(("h0", stripped[2:])) + elif line.startswith("## "): + tokens.append(("h1", stripped[3:])) + elif line.startswith("### "): + tokens.append(("h2", stripped[4:])) + elif line.startswith("#### "): + tokens.append(("h3", stripped[5:])) + elif re.match(r"^\|.*\|$", stripped): + if re.match(r"^\|[\s\-:|]+\|$", stripped): + tokens.append(("table_separator", stripped)) + else: + tokens.append(("table_row", stripped)) + elif re.match(r"^[\s]*[-*+]\s", line): + text = re.sub(r"^[\s]*[-*+]\s", "", line) + tokens.append(("list_item", text)) + elif re.match(r"^\d+\.\s", stripped): + text = re.sub(r"^\d+\.\s", "", stripped) + tokens.append(("list_item", text)) + else: + tokens.append(("paragraph", stripped)) + + return tokens + + +def add_header(doc: Document, title: str, version: str) -> None: + """Add document header with software name and version.""" + section = doc.sections[0] + header = section.header + header_para = header.paragraphs[0] + header_para.text = f"{title} {version}" + header_para.alignment = WD_ALIGN_PARAGRAPH.LEFT + for run in header_para.runs: + run.font.size = Pt(9) + + +def set_chinese_font(doc: Document) -> None: + """Set default Chinese font to SimHei.""" + style = doc.styles["Normal"] + style.font.name = "SimHei" + style._element.rPr.rFonts.set(qn("w:eastAsia"), "SimHei") + + +def add_table_from_rows(doc: Document, rows: List[str]) -> None: + """Add a simple table from pipe-separated row strings.""" + if not rows: + return + + parsed = [] + for row in rows: + cells = [c.strip() for c in row.strip("|").split("|")] + parsed.append(cells) + + if not parsed: + return + + num_cols = max(len(r) for r in parsed) + table = doc.add_table(rows=len(parsed), cols=num_cols) + table.style = "Table Grid" + + for i, row_data in enumerate(parsed): + for j, cell_text in enumerate(row_data): + if j < num_cols: + cell = table.cell(i, j) + cell.text = cell_text + for paragraph in cell.paragraphs: + for run in paragraph.runs: + run.font.size = Pt(9) + + +def build_document( + tokens: List[Tuple[str, str]], + title: str, + version: str, +) -> Document: + """Build the DOCX document from parsed markdown tokens.""" + doc = Document() + + for section in doc.sections: + section.top_margin = Inches(1.0) + section.bottom_margin = Inches(0.8) + section.left_margin = Inches(1.0) + section.right_margin = Inches(0.8) + + set_chinese_font(doc) + add_header(doc, title, version) + + pending_paragraph: List[str] = [] + pending_table_rows: List[str] = [] + + def flush_paragraph() -> None: + if pending_paragraph: + text = " ".join(pending_paragraph) + p = doc.add_paragraph(text) + for run in p.runs: + run.font.size = Pt(10.5) + pending_paragraph.clear() + + def flush_table() -> None: + if pending_table_rows: + add_table_from_rows(doc, pending_table_rows) + pending_table_rows.clear() + + for token_type, content in tokens: + if token_type in ("h0", "h1", "h2", "h3"): + flush_paragraph() + flush_table() + level = int(token_type[1]) + doc.add_heading(content, level=level) + + elif token_type == "code_start": + flush_paragraph() + flush_table() + + elif token_type == "code_end": + flush_paragraph() + + elif token_type == "code_line": + p = doc.add_paragraph() + run = p.add_run(content) + run.font.name = "Courier New" + run.font.size = Pt(8) + p.paragraph_format.space_before = Pt(0) + p.paragraph_format.space_after = Pt(0) + p.paragraph_format.line_spacing = 1.0 + + elif token_type == "list_item": + flush_paragraph() + flush_table() + p = doc.add_paragraph(content, style="List Bullet") + for run in p.runs: + run.font.size = Pt(10.5) + + elif token_type == "table_row": + flush_paragraph() + pending_table_rows.append(content) + + elif token_type == "table_separator": + pass # skip separator lines + + elif token_type == "hr": + flush_paragraph() + flush_table() + p = doc.add_paragraph() + p.add_run("—" * 40) + + elif token_type == "blank": + flush_paragraph() + flush_table() + + elif token_type == "paragraph": + flush_table() + pending_paragraph.append(content) + + flush_paragraph() + flush_table() + + return doc + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Generate software operation manual (DOCX) from README." + ) + parser.add_argument("--title", required=True, help="Software full name") + parser.add_argument("--version", default="V1.0", help="Software version") + parser.add_argument("--readme", required=True, help="Path to README.md") + parser.add_argument("--output", required=True, help="Output DOCX file path") + + args = parser.parse_args() + + if not DOCX_AVAILABLE: + print("Error: python-docx is required. Install with: pip install python-docx") + return 1 + + try: + with open(args.readme, "r", encoding="utf-8") as f: + content = f.read() + except FileNotFoundError: + print(f"Error: README file not found: {args.readme}") + return 1 + except Exception as exc: + print(f"Error reading README: {exc}") + return 1 + + tokens = parse_markdown(content) + doc = build_document(tokens, args.title, args.version) + doc.save(args.output) + + print(f"Operation manual created: {args.output}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/addons/officials/requirements.txt b/addons/officials/requirements.txt index 29c179f1..29824ce4 100644 --- a/addons/officials/requirements.txt +++ b/addons/officials/requirements.txt @@ -13,3 +13,7 @@ requests>=2.31.0 google-auth>=2.28.0 google-auth-oauthlib>=1.2.0 google-api-python-client>=2.100.0 + +# Software copyright registration (swcr-register skill) +python-docx>=1.1.0 +chardet>=5.0.0 diff --git a/config-templates/openclaw-aihubmix.json b/config-templates/openclaw-aihubmix.json index 66936bb5..49ee5a35 100644 --- a/config-templates/openclaw-aihubmix.json +++ b/config-templates/openclaw-aihubmix.json @@ -114,6 +114,11 @@ "ask": "off" } }, + "heartbeat": { + "every": "1d", + "target": "none", + "isolatedSession": true + }, "thinkingDefault": "high", "reasoningDefault": "off" }, diff --git a/config-templates/openclaw.json b/config-templates/openclaw.json index b4a53956..9c51de4b 100644 --- a/config-templates/openclaw.json +++ b/config-templates/openclaw.json @@ -170,6 +170,11 @@ "ask": "off" } }, + "heartbeat": { + "every": "1d", + "target": "none", + "isolatedSession": true + }, "thinkingDefault": "high", "reasoningDefault": "off" }, From 5b8de295ec376bcc477cc323852f462e3a0fca5e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 5 Jun 2026 04:45:29 +0000 Subject: [PATCH 2/2] chore: bump version to v5.5.0 [skip ci] --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 4db11301..268fccb1 100644 --- a/version +++ b/version @@ -1 +1 @@ -v5.4.9 +v5.5.0