codebee 0.1.94 → 0.1.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/CHANGELOG.md +725 -715
  2. package/LICENSE +21 -21
  3. package/README.md +515 -509
  4. package/app/core/acceptance.py +100 -100
  5. package/app/core/agent_context.py +100 -0
  6. package/app/core/aiflavor.py +197 -197
  7. package/app/core/analytics.py +38 -38
  8. package/app/core/artifacts.py +148 -148
  9. package/app/core/assets.py +110 -110
  10. package/app/core/attachments.py +623 -623
  11. package/app/core/audit.py +30 -30
  12. package/app/core/backup.py +470 -470
  13. package/app/core/beekeeper.py +183 -183
  14. package/app/core/benchstore.py +102 -95
  15. package/app/core/board.py +209 -209
  16. package/app/core/bookmeta.py +1036 -1036
  17. package/app/core/bookmeta_catalog.py +180 -180
  18. package/app/core/branching.py +137 -137
  19. package/app/core/builtin_agent.py +1680 -1680
  20. package/app/core/capability.py +144 -144
  21. package/app/core/chaptersafe.py +32 -32
  22. package/app/core/checkpoints.py +143 -0
  23. package/app/core/cleanup.py +303 -303
  24. package/app/core/compaction.py +201 -201
  25. package/app/core/contracts.py +255 -255
  26. package/app/core/covergen.py +356 -356
  27. package/app/core/defectretro.py +43 -43
  28. package/app/core/diagnostics.py +114 -114
  29. package/app/core/dispatch.py +198 -191
  30. package/app/core/dispatch_log.py +128 -128
  31. package/app/core/env_scrub.py +85 -85
  32. package/app/core/errorlog.py +137 -137
  33. package/app/core/eval_matrix.py +82 -0
  34. package/app/core/evalbench.py +184 -91
  35. package/app/core/evaluation.py +174 -174
  36. package/app/core/failure_notify.py +290 -290
  37. package/app/core/flow_graph.py +33 -0
  38. package/app/core/flows.py +8 -1
  39. package/app/core/goal_service.py +167 -167
  40. package/app/core/health.py +389 -389
  41. package/app/core/history.py +38 -38
  42. package/app/core/hooks.py +162 -162
  43. package/app/core/jobs.py +25 -4
  44. package/app/core/knowledge_pipeline.py +86 -0
  45. package/app/core/manager.py +2232 -2232
  46. package/app/core/mocks.py +64 -64
  47. package/app/core/notify.py +201 -201
  48. package/app/core/novel_quality.py +181 -189
  49. package/app/core/openapi.py +177 -154
  50. package/app/core/operations.py +228 -228
  51. package/app/core/paihang.py +145 -145
  52. package/app/core/pipeline.py +43 -19
  53. package/app/core/planner.py +17 -11
  54. package/app/core/policy.py +52 -0
  55. package/app/core/portguard.py +111 -111
  56. package/app/core/presence.py +77 -77
  57. package/app/core/preview.py +293 -293
  58. package/app/core/prompt_guard.py +78 -78
  59. package/app/core/publish/__init__.py +7 -7
  60. package/app/core/publish/auto.py +26 -3
  61. package/app/core/publish/browser.py +655 -655
  62. package/app/core/publish/chaprows.py +138 -0
  63. package/app/core/publish/fanqie.py +210 -165
  64. package/app/core/publish/flow.py +753 -640
  65. package/app/core/publish/flows-fanqie-calibrated.json +349 -336
  66. package/app/core/publish/ledger.py +13 -1
  67. package/app/core/publish/manager.py +1150 -1118
  68. package/app/core/publish/qimao.py +176 -176
  69. package/app/core/publish/volumes.py +108 -0
  70. package/app/core/publish/ws.py +139 -139
  71. package/app/core/quality_gate.py +194 -194
  72. package/app/core/redact.py +38 -38
  73. package/app/core/repeat_guard.py +129 -129
  74. package/app/core/retrieval.py +137 -137
  75. package/app/core/router.py +398 -398
  76. package/app/core/runner.py +2238 -2238
  77. package/app/core/selfupdate.py +384 -384
  78. package/app/core/session_log.py +161 -161
  79. package/app/core/sessions.py +312 -312
  80. package/app/core/settings.py +20 -0
  81. package/app/core/settings_schema.py +270 -270
  82. package/app/core/share_page.py +114 -114
  83. package/app/core/skill_scan.py +139 -139
  84. package/app/core/skillpacks/fanqie-novel.md +8 -6
  85. package/app/core/skillpacks/market/character-bible.md +66 -66
  86. package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
  87. package/app/core/skillpacks/market/git-workflow.md +57 -57
  88. package/app/core/skillpacks/market/release-notes.md +72 -72
  89. package/app/core/skillpacks/market/weekly-report.md +71 -71
  90. package/app/core/skillpacks/market/worldview-consistency.md +70 -70
  91. package/app/core/skillpacks/qimao-signing.md +14 -11
  92. package/app/core/skills.py +32 -21
  93. package/app/core/step_runner.py +132 -132
  94. package/app/core/store.py +9 -0
  95. package/app/core/story_tracking.py +275 -275
  96. package/app/core/task_compile.py +199 -199
  97. package/app/core/telemetry.py +292 -292
  98. package/app/core/tlsctx.py +64 -64
  99. package/app/core/token_meter.py +147 -147
  100. package/app/core/tracing.py +207 -144
  101. package/app/core/upstream.py +41 -41
  102. package/app/core/usage.py +1038 -1038
  103. package/app/core/volumes.py +389 -389
  104. package/app/core/webhooks.py +89 -0
  105. package/app/core/wxdigest.py +858 -858
  106. package/app/core/zentao.py +2692 -2692
  107. package/app/main.py +104 -9
  108. package/app/mcp_server.py +244 -244
  109. package/app/pick_dialog.py +110 -110
  110. package/app/ui/app.js +287 -19
  111. package/app/ui/artdiff.js +74 -74
  112. package/app/ui/assets/community-qr.png +0 -0
  113. package/app/ui/assets/donate-alipay.jpg +0 -0
  114. package/app/ui/assets/donate-qr.jpg +0 -0
  115. package/app/ui/board.html +162 -162
  116. package/app/ui/board.js +305 -305
  117. package/app/ui/i18n.js +33 -0
  118. package/app/ui/icons/bee.svg +79 -79
  119. package/app/ui/index.html +32 -3
  120. package/app/ui/manifest.json +16 -16
  121. package/app/ui/qrcode.js +2297 -2297
  122. package/app/ui/style.css +196 -2
  123. package/bin/tutti.js +147 -147
  124. package/package.json +48 -48
  125. package/tools/wx_reader.py +261 -261
  126. package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
  127. package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
  128. package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
  129. package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
  130. package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
  131. package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
  132. package/app/.playwright-cli/run-detail-after.png +0 -0
  133. package/app/output/_createbook_body.json +0 -1
  134. package/app/output/_probe_fq_click.py +0 -50
  135. package/app/output/_probe_fq_form.py +0 -42
  136. package/app/output/_probe_fq_modal.py +0 -55
  137. package/app/output/_probe_fq_points.py +0 -71
  138. package/app/output/_probe_fq_submit.py +0 -34
  139. package/app/output/_probe_fq_watch.py +0 -62
@@ -1,110 +1,110 @@
1
- #!/usr/bin/env python3
2
- """原生「选择文件夹」对话框(main.py 的 /api/pick_folder 用)。
3
-
4
- Tk 必须活在自家进程的主线程里:HTTP 请求线程里建 root 在 macOS 上会崩,
5
- Windows 上反复建/销毁也不稳,所以隔离成独立进程。ask_directory() 是父端
6
- 入口,把本文件拉起为子进程,请求参数(JSON:initial 起始目录 / title 标题)
7
- 走 stdin,选中目录以 JSON({"path": ...})走 stdout,用户取消回空串;
8
- 对话框部分是文件尾的 main()。任何一步失败都以非零码退出,父端据此回
9
- fallback=true 让前端回落网页目录弹框。"""
10
- import json
11
- import os
12
- import subprocess
13
- import sys
14
- from pathlib import Path
15
-
16
- # 非 Windows 置 0:POSIX 的 Popen 对非零 creationflags 抛 ValueError(manager 同款守卫)
17
- CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0
18
- _SELF = str(Path(__file__).resolve())
19
-
20
-
21
- def ask_directory(initial="", title="选择文件夹"):
22
- """父端入口:拉起子进程弹原生对话框,返回 (path, error, fallback)。
23
-
24
- 用户取消时 path 为空串;子进程起不来(机器无 tkinter 等)时 error
25
- 非空且 fallback=True,调用方原样转给前端回落网页目录弹框。"""
26
- if not Path(__file__).exists():
27
- return "", "pick_dialog.py 缺失", True
28
- req = json.dumps({"initial": initial, "title": title}).encode("utf-8")
29
- env = dict(os.environ, PYTHONIOENCODING="utf-8")
30
- try:
31
- if os.name == "nt":
32
- cp = subprocess.run([sys.executable, _SELF], input=req,
33
- capture_output=True, timeout=600, env=env,
34
- creationflags=CREATE_NO_WINDOW)
35
- else:
36
- cp = subprocess.run([sys.executable, _SELF], input=req,
37
- capture_output=True, timeout=600, env=env)
38
- except Exception as e:
39
- return "", str(e), True
40
- if cp.returncode != 0:
41
- err = (cp.stderr or b"").decode("utf-8", "replace").strip().splitlines()
42
- return "", (err[-1] if err else "native picker unavailable"), True
43
- try:
44
- data = json.loads((cp.stdout or b"").decode("utf-8", "replace") or "{}")
45
- except Exception:
46
- return "", "对话框输出无法解析", True
47
- return str(data.get("path") or ""), "", False
48
-
49
-
50
- def ask_file(initial="", title="选择文件"):
51
- """父端入口:拉起子进程弹原生「选择文件」对话框(导入备份包用)。
52
- 返回 (path, error, fallback),语义与 ask_directory 一致。"""
53
- if not Path(__file__).exists():
54
- return "", "pick_dialog.py 缺失", True
55
- req = json.dumps({"mode": "file", "initial": initial, "title": title}).encode("utf-8")
56
- env = dict(os.environ, PYTHONIOENCODING="utf-8")
57
- try:
58
- if os.name == "nt":
59
- cp = subprocess.run([sys.executable, _SELF], input=req,
60
- capture_output=True, timeout=600, env=env,
61
- creationflags=CREATE_NO_WINDOW)
62
- else:
63
- cp = subprocess.run([sys.executable, _SELF], input=req,
64
- capture_output=True, timeout=600, env=env)
65
- except Exception as e:
66
- return "", str(e), True
67
- if cp.returncode != 0:
68
- err = (cp.stderr or b"").decode("utf-8", "replace").strip().splitlines()
69
- return "", (err[-1] if err else "native picker unavailable"), True
70
- try:
71
- data = json.loads((cp.stdout or b"").decode("utf-8", "replace") or "{}")
72
- except Exception:
73
- return "", "对话框输出无法解析", True
74
- return str(data.get("path") or ""), "", False
75
-
76
-
77
- def main():
78
- req = {}
79
- try:
80
- req = json.loads(sys.stdin.read() or "{}")
81
- except Exception:
82
- pass
83
- import tkinter as tk
84
- from tkinter import filedialog
85
-
86
- root = tk.Tk()
87
- root.withdraw()
88
- try:
89
- root.attributes("-topmost", True) # 别被全屏浏览器盖住
90
- except Exception:
91
- pass
92
- is_file = req.get("mode") == "file"
93
- kw = {"title": req.get("title") or ("选择文件" if is_file else "选择文件夹")}
94
- init = req.get("initial") or ""
95
- if init and os.path.isdir(init):
96
- kw["initialdir"] = init
97
- try:
98
- if is_file:
99
- kw["filetypes"] = [("备份包", "*.zip"), ("全部文件", "*.*")]
100
- path = filedialog.askopenfilename(**kw) or ""
101
- else:
102
- path = filedialog.askdirectory(**kw) or ""
103
- except Exception:
104
- sys.exit(1)
105
- sys.stdout.write(json.dumps({"path": path}))
106
- root.destroy()
107
-
108
-
109
- if __name__ == "__main__":
110
- main()
1
+ #!/usr/bin/env python3
2
+ """原生「选择文件夹」对话框(main.py 的 /api/pick_folder 用)。
3
+
4
+ Tk 必须活在自家进程的主线程里:HTTP 请求线程里建 root 在 macOS 上会崩,
5
+ Windows 上反复建/销毁也不稳,所以隔离成独立进程。ask_directory() 是父端
6
+ 入口,把本文件拉起为子进程,请求参数(JSON:initial 起始目录 / title 标题)
7
+ 走 stdin,选中目录以 JSON({"path": ...})走 stdout,用户取消回空串;
8
+ 对话框部分是文件尾的 main()。任何一步失败都以非零码退出,父端据此回
9
+ fallback=true 让前端回落网页目录弹框。"""
10
+ import json
11
+ import os
12
+ import subprocess
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ # 非 Windows 置 0:POSIX 的 Popen 对非零 creationflags 抛 ValueError(manager 同款守卫)
17
+ CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0
18
+ _SELF = str(Path(__file__).resolve())
19
+
20
+
21
+ def ask_directory(initial="", title="选择文件夹"):
22
+ """父端入口:拉起子进程弹原生对话框,返回 (path, error, fallback)。
23
+
24
+ 用户取消时 path 为空串;子进程起不来(机器无 tkinter 等)时 error
25
+ 非空且 fallback=True,调用方原样转给前端回落网页目录弹框。"""
26
+ if not Path(__file__).exists():
27
+ return "", "pick_dialog.py 缺失", True
28
+ req = json.dumps({"initial": initial, "title": title}).encode("utf-8")
29
+ env = dict(os.environ, PYTHONIOENCODING="utf-8")
30
+ try:
31
+ if os.name == "nt":
32
+ cp = subprocess.run([sys.executable, _SELF], input=req,
33
+ capture_output=True, timeout=600, env=env,
34
+ creationflags=CREATE_NO_WINDOW)
35
+ else:
36
+ cp = subprocess.run([sys.executable, _SELF], input=req,
37
+ capture_output=True, timeout=600, env=env)
38
+ except Exception as e:
39
+ return "", str(e), True
40
+ if cp.returncode != 0:
41
+ err = (cp.stderr or b"").decode("utf-8", "replace").strip().splitlines()
42
+ return "", (err[-1] if err else "native picker unavailable"), True
43
+ try:
44
+ data = json.loads((cp.stdout or b"").decode("utf-8", "replace") or "{}")
45
+ except Exception:
46
+ return "", "对话框输出无法解析", True
47
+ return str(data.get("path") or ""), "", False
48
+
49
+
50
+ def ask_file(initial="", title="选择文件"):
51
+ """父端入口:拉起子进程弹原生「选择文件」对话框(导入备份包用)。
52
+ 返回 (path, error, fallback),语义与 ask_directory 一致。"""
53
+ if not Path(__file__).exists():
54
+ return "", "pick_dialog.py 缺失", True
55
+ req = json.dumps({"mode": "file", "initial": initial, "title": title}).encode("utf-8")
56
+ env = dict(os.environ, PYTHONIOENCODING="utf-8")
57
+ try:
58
+ if os.name == "nt":
59
+ cp = subprocess.run([sys.executable, _SELF], input=req,
60
+ capture_output=True, timeout=600, env=env,
61
+ creationflags=CREATE_NO_WINDOW)
62
+ else:
63
+ cp = subprocess.run([sys.executable, _SELF], input=req,
64
+ capture_output=True, timeout=600, env=env)
65
+ except Exception as e:
66
+ return "", str(e), True
67
+ if cp.returncode != 0:
68
+ err = (cp.stderr or b"").decode("utf-8", "replace").strip().splitlines()
69
+ return "", (err[-1] if err else "native picker unavailable"), True
70
+ try:
71
+ data = json.loads((cp.stdout or b"").decode("utf-8", "replace") or "{}")
72
+ except Exception:
73
+ return "", "对话框输出无法解析", True
74
+ return str(data.get("path") or ""), "", False
75
+
76
+
77
+ def main():
78
+ req = {}
79
+ try:
80
+ req = json.loads(sys.stdin.read() or "{}")
81
+ except Exception:
82
+ pass
83
+ import tkinter as tk
84
+ from tkinter import filedialog
85
+
86
+ root = tk.Tk()
87
+ root.withdraw()
88
+ try:
89
+ root.attributes("-topmost", True) # 别被全屏浏览器盖住
90
+ except Exception:
91
+ pass
92
+ is_file = req.get("mode") == "file"
93
+ kw = {"title": req.get("title") or ("选择文件" if is_file else "选择文件夹")}
94
+ init = req.get("initial") or ""
95
+ if init and os.path.isdir(init):
96
+ kw["initialdir"] = init
97
+ try:
98
+ if is_file:
99
+ kw["filetypes"] = [("备份包", "*.zip"), ("全部文件", "*.*")]
100
+ path = filedialog.askopenfilename(**kw) or ""
101
+ else:
102
+ path = filedialog.askdirectory(**kw) or ""
103
+ except Exception:
104
+ sys.exit(1)
105
+ sys.stdout.write(json.dumps({"path": path}))
106
+ root.destroy()
107
+
108
+
109
+ if __name__ == "__main__":
110
+ main()