sophhub 0.4.19 → 0.4.21

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 (633) hide show
  1. package/README.md +199 -187
  2. package/agents/ai-cs-admin/.config.json +51 -51
  3. package/agents/ai-cs-admin/AGENTS.md +293 -293
  4. package/agents/ai-cs-admin/HEARTBEAT.md +18 -18
  5. package/agents/ai-cs-qa/.config.json +47 -47
  6. package/agents/ai-cs-qa/BOOTSTRAP.md +22 -22
  7. package/agents/ai-cs-qa/scripts/setup_links.sh +39 -39
  8. package/agents/beauty/.config.json +17 -17
  9. package/agents/beauty/AGENTS.md +234 -234
  10. package/agents/beauty/BOOTSTRAP.md +55 -55
  11. package/agents/beauty/HEARTBEAT.md +5 -5
  12. package/agents/beauty/IDENTITY.md +5 -5
  13. package/agents/beauty/MEMORY.md +44 -44
  14. package/agents/beauty/SOUL.md +64 -64
  15. package/agents/beauty/TOOLS.md +160 -160
  16. package/agents/beauty/USER.md +114 -114
  17. package/agents/intern-admin/.config.json +60 -60
  18. package/agents/intern-admin/AGENTS.md +267 -267
  19. package/agents/intern-admin/BOOTSTRAP.md +21 -21
  20. package/agents/intern-admin/HEARTBEAT.md +3 -3
  21. package/agents/intern-admin/IDENTITY.md +6 -6
  22. package/agents/intern-admin/MEMORY.md +21 -21
  23. package/agents/intern-admin/SOUL.md +23 -23
  24. package/agents/intern-admin/TOOLS.md +93 -93
  25. package/agents/intern-admin/USER.md +16 -16
  26. package/agents/intern-admin/scripts/init_workspace.sh +27 -27
  27. package/agents/intern-qa/.config.json +46 -46
  28. package/agents/intern-qa/AGENTS.md +303 -303
  29. package/agents/intern-qa/BOOTSTRAP.md +16 -16
  30. package/agents/intern-qa/HEARTBEAT.md +3 -3
  31. package/agents/intern-qa/IDENTITY.md +6 -6
  32. package/agents/intern-qa/MEMORY.md +22 -22
  33. package/agents/intern-qa/SOUL.md +24 -24
  34. package/agents/intern-qa/TOOLS.md +24 -24
  35. package/agents/intern-qa/USER.md +27 -27
  36. package/agents/intern-qa/scripts/setup_links.sh +54 -54
  37. package/agents/parent-toddler/.config.json +37 -37
  38. package/agents/parent-toddler/AGENTS.md +51 -51
  39. package/agents/parent-toddler/BOOTSTRAP.md +55 -55
  40. package/agents/parent-toddler/HEARTBEAT.md +5 -5
  41. package/agents/parent-toddler/IDENTITY.md +5 -5
  42. package/agents/parent-toddler/MEMORY.md +22 -22
  43. package/agents/parent-toddler/SOUL.md +35 -35
  44. package/agents/parent-toddler/TOOLS.md +31 -31
  45. package/agents/parent-toddler/USER.md +44 -44
  46. package/agents/vip-admin/.config.json +51 -51
  47. package/agents/vip-admin/AGENTS.md +314 -314
  48. package/agents/vip-admin/BOOTSTRAP.md +21 -21
  49. package/agents/vip-admin/HEARTBEAT.md +19 -19
  50. package/agents/vip-admin/IDENTITY.md +6 -6
  51. package/agents/vip-admin/MEMORY.md +30 -30
  52. package/agents/vip-admin/SOUL.md +25 -25
  53. package/agents/vip-admin/TOOLS.md +108 -108
  54. package/agents/vip-admin/USER.md +31 -31
  55. package/agents/vip-qa/.config.json +58 -58
  56. package/agents/vip-qa/AGENTS.md +319 -319
  57. package/agents/vip-qa/BOOTSTRAP.md +73 -73
  58. package/agents/vip-qa/HEARTBEAT.md +23 -23
  59. package/agents/vip-qa/IDENTITY.md +7 -7
  60. package/agents/vip-qa/MEMORY.md +23 -23
  61. package/agents/vip-qa/SOUL.md +34 -34
  62. package/agents/vip-qa/TOOLS.md +41 -41
  63. package/agents/vip-qa/USER.md +16 -16
  64. package/agents/vip-qa/scripts/setup_links.sh +39 -39
  65. package/bin/sophhub.js +25 -25
  66. package/package.json +35 -33
  67. package/skills/agent-install/skill.json +34 -34
  68. package/skills/agent-install/src/SKILL.md +240 -240
  69. package/skills/agent-install/src/pyproject.toml +6 -6
  70. package/skills/agent-install/src/scripts/backup_agent.py +120 -120
  71. package/skills/agent-install/src/scripts/check_installed.py +479 -479
  72. package/skills/agent-install/src/scripts/common.py +568 -568
  73. package/skills/agent-install/src/scripts/copy_agent_files.py +59 -59
  74. package/skills/agent-install/src/scripts/list_agents.py +285 -285
  75. package/skills/agent-install/src/scripts/resolve_install_params.py +90 -90
  76. package/skills/agent-install/src/scripts/update_agent_md.py +76 -76
  77. package/skills/agent-install/src/scripts/update_openclaw.py +193 -193
  78. package/skills/agent-install/src/scripts/verify_download.py +148 -148
  79. package/skills/aippt/skill.json +20 -20
  80. package/skills/aippt/src/SKILL.md +235 -235
  81. package/skills/aippt/src/pyproject.toml +8 -8
  82. package/skills/aippt/src/scripts/auth.py +122 -122
  83. package/skills/aippt/src/scripts/ppt.py +361 -361
  84. package/skills/aippt/src/scripts/provider_docmee.py +299 -299
  85. package/skills/beauty-salon-inventory/skill.json +16 -16
  86. package/skills/beauty-salon-inventory/src/SKILL.md +69 -69
  87. package/skills/beauty-salon-inventory/src/scripts/init_salon_inventory.py +39 -39
  88. package/skills/beauty-salon-inventory/src/scripts/init_salon_inventory.sh +4 -4
  89. package/skills/beauty-salon-inventory/src/scripts/salon_inventory_cli.py +244 -244
  90. package/skills/beauty-salon-marketing/skill.json +10 -10
  91. package/skills/beauty-salon-marketing/src/SKILL.md +36 -36
  92. package/skills/beauty-salon-marketing/src/playbooks/beauty-salon-festival.md +19 -19
  93. package/skills/beauty-salon-marketing/src/playbooks/beauty-salon-segment.md +18 -18
  94. package/skills/beauty-salon-marketing/src/scripts/beauty_marketing_cli.py +99 -99
  95. package/skills/beauty-salon-marketing/src/scripts/member_segment.py +114 -114
  96. package/skills/beauty-salon-member-appointment/skill.json +10 -10
  97. package/skills/beauty-salon-member-appointment/src/SKILL.md +36 -36
  98. package/skills/beauty-salon-member-appointment/src/pyproject.toml +9 -9
  99. package/skills/beauty-salon-member-appointment/src/scripts/run_e2e_smoke.py +160 -160
  100. package/skills/beauty-salon-member-appointment/src/src/member_appt_cli/__init__.py +1 -1
  101. package/skills/beauty-salon-member-appointment/src/src/member_appt_cli/__main__.py +4 -4
  102. package/skills/beauty-salon-member-appointment/src/src/member_appt_cli/cli.py +921 -921
  103. package/skills/beauty-salon-member-appointment/src/src/member_appt_cli/db.py +30 -30
  104. package/skills/beauty-salon-membership/skill.json +20 -20
  105. package/skills/beauty-salon-membership/src/SKILL.md +67 -67
  106. package/skills/beauty-salon-product-service/skill.json +12 -12
  107. package/skills/beauty-salon-product-service/src/SKILL.md +42 -42
  108. package/skills/beauty-salon-product-service/src/pyproject.toml +9 -9
  109. package/skills/beauty-salon-product-service/src/src/product_service_cli/__init__.py +1 -1
  110. package/skills/beauty-salon-product-service/src/src/product_service_cli/__main__.py +4 -4
  111. package/skills/beauty-salon-product-service/src/src/product_service_cli/cli.py +329 -329
  112. package/skills/beauty-salon-product-service/src/src/product_service_cli/db.py +29 -29
  113. package/skills/beauty-salon-staff/skill.json +10 -10
  114. package/skills/beauty-salon-staff/src/SKILL.md +37 -37
  115. package/skills/beauty-salon-staff/src/pyproject.toml +9 -9
  116. package/skills/beauty-salon-staff/src/src/staff_cli/__init__.py +1 -1
  117. package/skills/beauty-salon-staff/src/src/staff_cli/__main__.py +4 -4
  118. package/skills/beauty-salon-staff/src/src/staff_cli/cli.py +479 -479
  119. package/skills/beauty-salon-staff/src/src/staff_cli/db.py +28 -28
  120. package/skills/beauty-salon-suite/skill.json +13 -13
  121. package/skills/beauty-salon-suite/src/SKILL.md +18 -18
  122. package/skills/beauty-salon-suite/src/beauty_db/__init__.py +2 -2
  123. package/skills/beauty-salon-suite/src/beauty_db/db.py +249 -249
  124. package/skills/beauty-salon-traffic/skill.json +20 -20
  125. package/skills/beauty-salon-traffic/src/SKILL.md +84 -84
  126. package/skills/bing-image-search/skill.json +20 -20
  127. package/skills/bing-image-search/src/SKILL.md +105 -105
  128. package/skills/bot-api-status/skill.json +44 -44
  129. package/skills/bot-api-status/src/SKILL.md +99 -99
  130. package/skills/bot-api-status/src/pyproject.toml +5 -5
  131. package/skills/bot-api-status/src/scripts/secret.py +496 -496
  132. package/skills/bot-secret/skill.json +35 -35
  133. package/skills/bot-secret/src/SKILL.md +51 -51
  134. package/skills/bot-secret/src/pyproject.toml +5 -5
  135. package/skills/bot-secret/src/scripts/secret.py +120 -120
  136. package/skills/cake-flower-holiday-campaign/skill.json +20 -20
  137. package/skills/cake-flower-holiday-campaign/src/SKILL.md +68 -68
  138. package/skills/cake-flower-order-sop/skill.json +20 -20
  139. package/skills/cake-flower-order-sop/src/SKILL.md +65 -65
  140. package/skills/claw-agent-get-send/skill.json +32 -32
  141. package/skills/claw-agent-get-send/src/SKILL.md +43 -43
  142. package/skills/claw-agent-get-send/src/pyproject.toml +5 -5
  143. package/skills/claw-agent-get-send/src/scripts/appia_claw.py +379 -379
  144. package/skills/compact-context/skill.json +20 -20
  145. package/skills/compact-context/src/SKILL.md +133 -133
  146. package/skills/compact-context/src/scripts/check.sh +381 -381
  147. package/skills/compact-context/src/scripts/set-keep-recent.mjs +1337 -1337
  148. package/skills/compact-context/src/scripts/setup.sh +96 -96
  149. package/skills/consensus/skill.json +20 -20
  150. package/skills/consensus/src/SKILL.md +93 -93
  151. package/skills/deepwiki/skill.json +20 -20
  152. package/skills/deepwiki/src/SKILL.md +45 -45
  153. package/skills/deepwiki/src/_meta.json +5 -5
  154. package/skills/deepwiki/src/scripts/deepwiki.js +135 -135
  155. package/skills/didi-ride/skill.json +20 -20
  156. package/skills/didi-ride/src/SKILL.md +309 -309
  157. package/skills/didi-ride/src/_meta.json +5 -5
  158. package/skills/didi-ride/src/assets/PREFERENCE.md +58 -58
  159. package/skills/didi-ride/src/package.json +15 -15
  160. package/skills/didi-ride/src/references/api_references.md +171 -171
  161. package/skills/didi-ride/src/references/error_handling.md +68 -68
  162. package/skills/didi-ride/src/references/setup.md +73 -73
  163. package/skills/didi-ride/src/references/workflow.md +150 -150
  164. package/skills/feishu-bitable/skill.json +20 -20
  165. package/skills/feishu-bitable/src/CHECKLIST.md +149 -149
  166. package/skills/feishu-bitable/src/README.md +177 -177
  167. package/skills/feishu-bitable/src/SKILL.md +113 -113
  168. package/skills/feishu-bitable/src/_meta.json +5 -5
  169. package/skills/feishu-bitable/src/api.js +380 -380
  170. package/skills/feishu-bitable/src/bin/cli.js +283 -283
  171. package/skills/feishu-bitable/src/description.md +142 -142
  172. package/skills/feishu-bitable/src/examples/create-records.json +51 -51
  173. package/skills/feishu-bitable/src/examples/create-table.json +63 -63
  174. package/skills/feishu-bitable/src/package-lock.json +324 -324
  175. package/skills/feishu-bitable/src/package.json +32 -32
  176. package/skills/feishu-bitable/src/publish-config.json +13 -13
  177. package/skills/feishu-bitable/src/test-simple.js +60 -60
  178. package/skills/feishu-bitable/src/utils.js +260 -260
  179. package/skills/feishu-notes-assistant-universal/skill.json +20 -20
  180. package/skills/feishu-notes-assistant-universal/src/README.md +55 -55
  181. package/skills/feishu-notes-assistant-universal/src/SKILL.md +159 -159
  182. package/skills/feishu-notes-assistant-universal/src/scripts/_resolve_lark_cli.py +58 -58
  183. package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_meeting_minutes.py +462 -462
  184. package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_notes_crud.py +547 -547
  185. package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_notes_crud_test.py +181 -181
  186. package/skills/feishu-notes-assistant-universal/src/scripts/run_meeting_minutes.py +80 -80
  187. package/skills/feishu-notes-assistant-universal/src/scripts/run_meeting_minutes.sh +5 -5
  188. package/skills/feishu-notes-assistant-universal/src/scripts/run_note_crud.py +32 -32
  189. package/skills/feishu-notes-assistant-universal/src/scripts/run_note_crud.sh +5 -5
  190. package/skills/flight-booking/skill.json +36 -36
  191. package/skills/flight-booking/src/SKILL.md +288 -288
  192. package/skills/flight-booking/src/scripts/flight_booking.py +1237 -1237
  193. package/skills/flyai/skill.json +20 -20
  194. package/skills/flyai/src/SKILL.md +119 -119
  195. package/skills/flyai/src/references/fliggy-fast-search.md +53 -53
  196. package/skills/flyai/src/references/search-flight.md +89 -89
  197. package/skills/flyai/src/references/search-hotels.md +57 -57
  198. package/skills/flyai/src/references/search-poi.md +48 -48
  199. package/skills/google-maps/skill.json +20 -20
  200. package/skills/google-maps/src/SKILL.md +237 -237
  201. package/skills/google-maps/src/_meta.json +5 -5
  202. package/skills/google-maps/src/lib/map_helper.py +912 -912
  203. package/skills/image-classify/skill.json +42 -42
  204. package/skills/image-classify/src/SKILL.md +368 -368
  205. package/skills/image-classify/src/references/config.json +4 -4
  206. package/skills/image-classify/src/scripts/face_search.py +1276 -1276
  207. package/skills/image-description/skill.json +34 -34
  208. package/skills/image-description/src/SKILL.md +33 -33
  209. package/skills/image-description/src/pyproject.toml +8 -8
  210. package/skills/image-description/src/scripts/ana_image.py +112 -112
  211. package/skills/image-identify-world/skill.json +20 -20
  212. package/skills/image-identify-world/src/SKILL.md +40 -40
  213. package/skills/image-identify-world/src/pyproject.toml +8 -8
  214. package/skills/image-identify-world/src/scripts/identify_world.py +115 -115
  215. package/skills/insurance-policy-review/skill.json +27 -27
  216. package/skills/insurance-policy-review/src/SKILL.md +75 -75
  217. package/skills/insurance-sales-playbook/skill.json +20 -20
  218. package/skills/insurance-sales-playbook/src/SKILL.md +58 -58
  219. package/skills/inventory-management/skill.json +20 -20
  220. package/skills/inventory-management/src/SKILL.md +241 -241
  221. package/skills/inventory-management/src/scripts/inventory.py +1844 -1844
  222. package/skills/large-task-router/skill.json +20 -20
  223. package/skills/large-task-router/src/SKILL.md +79 -79
  224. package/skills/large-task-router/src/templates/plan.md +74 -74
  225. package/skills/lawding-contract-review/skill.json +20 -20
  226. package/skills/lawding-contract-review/src/SKILL.md +284 -284
  227. package/skills/lawding-contract-review/src/references/legal-language-library.md +1385 -1385
  228. package/skills/lawding-contract-review/src/scripts/build_reminders.py +471 -471
  229. package/skills/lawding-contract-review/src/scripts/register_contract_cron.py +457 -457
  230. package/skills/md2pdf-converter/skill.json +20 -20
  231. package/skills/md2pdf-converter/src/SKILL.md +244 -244
  232. package/skills/md2pdf-converter/src/_meta.json +5 -5
  233. package/skills/md2pdf-converter/src/scripts/generate_emoji_mapping.py +74 -74
  234. package/skills/md2pdf-converter/src/scripts/md2pdf-local.sh +291 -291
  235. package/skills/notes-hub-assistant/skill.json +20 -20
  236. package/skills/notes-hub-assistant/src/SKILL.md +233 -233
  237. package/skills/notes-hub-assistant/src/scripts/_resolve_lark_cli.py +48 -48
  238. package/skills/notes-hub-assistant/src/scripts/openclaw_meeting_minutes.py +473 -473
  239. package/skills/notes-hub-assistant/src/scripts/openclaw_notes_crud.py +596 -596
  240. package/skills/notes-hub-assistant/src/scripts/openclaw_wolai_notes_crud.py +364 -364
  241. package/skills/notes-hub-assistant/src/scripts/run_meeting_minutes.py +79 -79
  242. package/skills/notes-hub-assistant/src/scripts/run_note_crud.py +37 -37
  243. package/skills/notes-hub-assistant/src/scripts/run_notionbot.py +36 -36
  244. package/skills/notes-hub-assistant/src/scripts/run_wolai_note_crud.py +27 -27
  245. package/skills/schedule-reminder/skill.json +20 -20
  246. package/skills/schedule-reminder/src/SKILL.md +619 -619
  247. package/skills/schedule-reminder/src/schedule_template.md +68 -68
  248. package/skills/schedule-reminder/src/scripts/append_event.py +204 -204
  249. package/skills/schedule-reminder/src/scripts/create_reminders.sh +163 -163
  250. package/skills/schedule-reminder/src/scripts/daily_activate.sh +175 -175
  251. package/skills/schedule-reminder/src/scripts/parse_schedule.py +704 -704
  252. package/skills/schedule-reminder/src/scripts/setup.sh +242 -242
  253. package/skills/schedule-reminder/src//347/224/250/346/210/267/346/214/207/345/215/227.md +311 -311
  254. package/skills/sessions-analysis/skill.json +34 -34
  255. package/skills/sessions-analysis/src/SKILL.md +81 -81
  256. package/skills/sessions-analysis/src/pyproject.toml +5 -5
  257. package/skills/sessions-analysis/src/scripts/ana_logs.py +205 -205
  258. package/skills/share-skill/skill.json +20 -20
  259. package/skills/share-skill/src/SKILL.md +261 -261
  260. package/skills/share-skill/src/scripts/share_skill_to_friend.py +1031 -1031
  261. package/skills/skill-creator/skill.json +20 -20
  262. package/skills/skill-creator/src/SKILL.md +370 -370
  263. package/skills/skill-creator/src/license.txt +202 -202
  264. package/skills/skill-creator/src/scripts/init_skill.py +378 -378
  265. package/skills/skill-creator/src/scripts/package_skill.py +111 -111
  266. package/skills/skill-creator/src/scripts/quick_validate.py +101 -101
  267. package/skills/skillhub/skill.json +27 -27
  268. package/skills/skillhub/src/SKILL.md +121 -121
  269. package/skills/sophnet-age-appearance/skill.json +20 -20
  270. package/skills/sophnet-age-appearance/src/SKILL.md +83 -83
  271. package/skills/sophnet-age-appearance/src/pyproject.toml +10 -10
  272. package/skills/sophnet-age-appearance/src/scripts/age_appearance.py +395 -395
  273. package/skills/sophnet-age-appearance/src/scripts/age_face_crop.py +313 -313
  274. package/skills/sophnet-bot-client/skill.json +20 -20
  275. package/skills/sophnet-bot-client/src/SKILL.md +255 -255
  276. package/skills/sophnet-bot-client/src/pyproject.toml +13 -13
  277. package/skills/sophnet-bot-client/src/scripts/bot_client_proxy.py +165 -165
  278. package/skills/sophnet-bot-client/src/scripts/bot_client_safe.sh +29 -29
  279. package/skills/sophnet-bot-client/src/scripts/bot_client_setup.py +502 -502
  280. package/skills/sophnet-bot-client/src/tests/test_bot_client_proxy.py +255 -255
  281. package/skills/sophnet-bot-client/src/tests/test_bot_client_setup.py +679 -679
  282. package/skills/sophnet-bot-client/src/uv.lock +8 -8
  283. package/skills/sophnet-customer-management/skill.json +20 -20
  284. package/skills/sophnet-customer-management/src/SKILL.md +270 -270
  285. package/skills/sophnet-customer-management/src/pyproject.toml +15 -15
  286. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/__init__.py +2 -2
  287. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/__main__.py +5 -5
  288. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/cli.py +67 -67
  289. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/commands/__init__.py +2 -2
  290. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/commands/customer.py +60 -60
  291. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/commands/export_file.py +18 -18
  292. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/commands/import_file.py +15 -15
  293. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/commands/reminder.py +26 -26
  294. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/commands/schema.py +28 -28
  295. package/skills/sophnet-customer-management/src/src/customer_mgmt_cli/config.py +54 -54
  296. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/__init__.py +2 -2
  297. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/exporter.py +85 -85
  298. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/models.py +84 -84
  299. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/normalizer.py +144 -144
  300. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/parser.py +241 -241
  301. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/query.py +109 -109
  302. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/reminder.py +121 -121
  303. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/repository.py +397 -397
  304. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/schema.py +106 -106
  305. package/skills/sophnet-customer-management/src/src/customer_mgmt_core/service.py +565 -565
  306. package/skills/sophnet-customer-management/src/uv.lock +48 -48
  307. package/skills/sophnet-customized-marketing/skill.json +28 -28
  308. package/skills/sophnet-customized-marketing/src/SKILL.md +144 -144
  309. package/skills/sophnet-customized-marketing/src/playbooks/campaign-planning.md +187 -187
  310. package/skills/sophnet-customized-marketing/src/playbooks/content-generation.md +124 -124
  311. package/skills/sophnet-customized-marketing/src/playbooks/marketing-calendar.md +59 -59
  312. package/skills/sophnet-customized-marketing/src/playbooks/multi-channel-bundle.md +94 -94
  313. package/skills/sophnet-customized-marketing/src/playbooks/poster-generation.md +182 -182
  314. package/skills/sophnet-customized-marketing/src/playbooks/style-profile-workflow.md +103 -103
  315. package/skills/sophnet-customized-marketing/src/pyproject.toml +8 -8
  316. package/skills/sophnet-customized-marketing/src/references/campaign-mechanics.md +168 -168
  317. package/skills/sophnet-customized-marketing/src/references/content-safety.md +26 -26
  318. package/skills/sophnet-customized-marketing/src/references/marketing-date-checklist.md +99 -99
  319. package/skills/sophnet-customized-marketing/src/references/platform-writing-guidelines.md +88 -88
  320. package/skills/sophnet-customized-marketing/src/references/quality-checklist.md +44 -44
  321. package/skills/sophnet-customized-marketing/src/scripts/generate_poster.py +572 -572
  322. package/skills/sophnet-customized-marketing/src/scripts/style_profile.py +215 -215
  323. package/skills/sophnet-dailynews/skill.json +20 -20
  324. package/skills/sophnet-dailynews/src/SKILL.md +179 -179
  325. package/skills/sophnet-dailynews/src/cache.json +150 -150
  326. package/skills/sophnet-dailynews/src/sources.json +230 -230
  327. package/skills/sophnet-docx/skill.json +20 -20
  328. package/skills/sophnet-docx/src/SKILL.md +463 -463
  329. package/skills/sophnet-docx/src/package-lock.json +208 -208
  330. package/skills/sophnet-docx/src/package.json +16 -16
  331. package/skills/sophnet-docx/src/pyproject.toml +11 -11
  332. package/skills/sophnet-docx/src/scripts/__init__.py +1 -1
  333. package/skills/sophnet-docx/src/scripts/accept_changes.py +135 -135
  334. package/skills/sophnet-docx/src/scripts/comment.py +318 -318
  335. package/skills/sophnet-docx/src/scripts/ensure_uv_env.sh +68 -68
  336. package/skills/sophnet-docx/src/scripts/office/helpers/merge_runs.py +199 -199
  337. package/skills/sophnet-docx/src/scripts/office/helpers/simplify_redlines.py +197 -197
  338. package/skills/sophnet-docx/src/scripts/office/pack.py +159 -159
  339. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -1499
  340. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -146
  341. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -1085
  342. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -11
  343. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -3081
  344. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -23
  345. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -185
  346. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -287
  347. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -1676
  348. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -28
  349. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -144
  350. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -174
  351. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -25
  352. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -18
  353. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -59
  354. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -56
  355. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -195
  356. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -582
  357. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -25
  358. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -4439
  359. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -570
  360. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -509
  361. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -12
  362. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -108
  363. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -96
  364. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -3646
  365. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -116
  366. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -42
  367. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -50
  368. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -49
  369. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -33
  370. package/skills/sophnet-docx/src/scripts/office/schemas/mce/mc.xsd +75 -75
  371. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2010.xsd +560 -560
  372. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2012.xsd +67 -67
  373. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2018.xsd +14 -14
  374. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -20
  375. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -13
  376. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -4
  377. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -8
  378. package/skills/sophnet-docx/src/scripts/office/soffice.py +183 -183
  379. package/skills/sophnet-docx/src/scripts/office/unpack.py +132 -132
  380. package/skills/sophnet-docx/src/scripts/office/validate.py +111 -111
  381. package/skills/sophnet-docx/src/scripts/office/validators/__init__.py +15 -15
  382. package/skills/sophnet-docx/src/scripts/office/validators/base.py +847 -847
  383. package/skills/sophnet-docx/src/scripts/office/validators/docx.py +446 -446
  384. package/skills/sophnet-docx/src/scripts/office/validators/pptx.py +275 -275
  385. package/skills/sophnet-docx/src/scripts/office/validators/redlining.py +247 -247
  386. package/skills/sophnet-docx/src/scripts/templates/comments.xml +3 -3
  387. package/skills/sophnet-docx/src/scripts/templates/commentsExtended.xml +3 -3
  388. package/skills/sophnet-docx/src/scripts/templates/commentsExtensible.xml +3 -3
  389. package/skills/sophnet-docx/src/scripts/templates/commentsIds.xml +3 -3
  390. package/skills/sophnet-docx/src/scripts/templates/people.xml +3 -3
  391. package/skills/sophnet-docx/src/scripts/upload_file.sh +96 -96
  392. package/skills/sophnet-docx/src/uv.lock +320 -320
  393. package/skills/sophnet-face-search/skill.json +20 -20
  394. package/skills/sophnet-face-search/src/SKILL.md +115 -115
  395. package/skills/sophnet-face-search/src/pyproject.toml +11 -11
  396. package/skills/sophnet-face-search/src/scripts/face_search.py +335 -335
  397. package/skills/sophnet-face-search/src/uv.lock +508 -508
  398. package/skills/sophnet-id-photo/skill.json +20 -20
  399. package/skills/sophnet-id-photo/src/SKILL.md +107 -107
  400. package/skills/sophnet-id-photo/src/pyproject.toml +10 -10
  401. package/skills/sophnet-id-photo/src/scripts/id_photo.py +540 -540
  402. package/skills/sophnet-id-photo/src/scripts/id_photo_compliance.py +215 -215
  403. package/skills/sophnet-id-photo/src/scripts/id_photo_face_crop.py +313 -313
  404. package/skills/sophnet-image-edit/skill.json +20 -20
  405. package/skills/sophnet-image-edit/src/SKILL.md +140 -140
  406. package/skills/sophnet-image-edit/src/pyproject.toml +9 -9
  407. package/skills/sophnet-image-edit/src/scripts/edit_and_preview.sh +68 -68
  408. package/skills/sophnet-image-edit/src/scripts/edit_image.py +279 -279
  409. package/skills/sophnet-image-edit/src/uv.lock +234 -234
  410. package/skills/sophnet-image-generate/skill.json +20 -20
  411. package/skills/sophnet-image-generate/src/SKILL.md +62 -62
  412. package/skills/sophnet-image-generate/src/pyproject.toml +9 -9
  413. package/skills/sophnet-image-generate/src/scripts/generate_image.py +156 -156
  414. package/skills/sophnet-image-generate/src/uv.lock +234 -234
  415. package/skills/sophnet-image-ocr/skill.json +20 -20
  416. package/skills/sophnet-image-ocr/src/SKILL.md +167 -167
  417. package/skills/sophnet-image-ocr/src/pyproject.toml +13 -13
  418. package/skills/sophnet-image-ocr/src/scripts/ocr.py +225 -225
  419. package/skills/sophnet-image-ocr/src/uv.lock +234 -234
  420. package/skills/sophnet-infinite-talk/skill.json +20 -20
  421. package/skills/sophnet-infinite-talk/src/SKILL.md +140 -140
  422. package/skills/sophnet-infinite-talk/src/pyproject.toml +9 -9
  423. package/skills/sophnet-infinite-talk/src/scripts/gen.py +172 -172
  424. package/skills/sophnet-oss/skill.json +27 -27
  425. package/skills/sophnet-oss/src/SKILL.md +118 -118
  426. package/skills/sophnet-oss/src/pyproject.toml +8 -8
  427. package/skills/sophnet-oss/src/scripts/upload_file.py +43 -43
  428. package/skills/sophnet-pdf/skill.json +20 -20
  429. package/skills/sophnet-pdf/src/SKILL.md +413 -413
  430. package/skills/sophnet-pdf/src/forms.md +297 -297
  431. package/skills/sophnet-pdf/src/pyproject.toml +14 -14
  432. package/skills/sophnet-pdf/src/reference.md +611 -611
  433. package/skills/sophnet-pdf/src/scripts/check_bounding_boxes.py +65 -65
  434. package/skills/sophnet-pdf/src/scripts/check_fillable_fields.py +11 -11
  435. package/skills/sophnet-pdf/src/scripts/convert_pdf_to_images.py +33 -33
  436. package/skills/sophnet-pdf/src/scripts/create_validation_image.py +37 -37
  437. package/skills/sophnet-pdf/src/scripts/enhance_tutorial.py +557 -557
  438. package/skills/sophnet-pdf/src/scripts/ensure_uv_env.sh +68 -68
  439. package/skills/sophnet-pdf/src/scripts/extract_form_field_info.py +122 -122
  440. package/skills/sophnet-pdf/src/scripts/extract_form_structure.py +115 -115
  441. package/skills/sophnet-pdf/src/scripts/extract_pdf_content.py +34 -34
  442. package/skills/sophnet-pdf/src/scripts/fill_fillable_fields.py +98 -98
  443. package/skills/sophnet-pdf/src/scripts/fill_pdf_form_with_annotations.py +107 -107
  444. package/skills/sophnet-pdf/src/scripts/upload_file.sh +88 -88
  445. package/skills/sophnet-pdf/src/uv.lock +537 -537
  446. package/skills/sophnet-qa-install/skill.json +27 -27
  447. package/skills/sophnet-qa-install/src/SKILL.md +210 -210
  448. package/skills/sophnet-qa-install/src/pyproject.toml +6 -6
  449. package/skills/sophnet-qa-install/src/scripts/backup_md.py +35 -35
  450. package/skills/sophnet-qa-install/src/scripts/check_installed.py +143 -143
  451. package/skills/sophnet-qa-install/src/scripts/update_config.py +142 -142
  452. package/skills/sophnet-qa-install/src/scripts/update_md.py +73 -73
  453. package/skills/sophnet-schedule/skill.json +20 -20
  454. package/skills/sophnet-schedule/src/ARCHITECTURE.md +321 -321
  455. package/skills/sophnet-schedule/src/IMPROVEMENTS.md +145 -145
  456. package/skills/sophnet-schedule/src/SKILL.md +1050 -1050
  457. package/skills/sophnet-schedule/src/_meta.json +6 -6
  458. package/skills/sophnet-schedule/src/api/models.py +245 -245
  459. package/skills/sophnet-schedule/src/apps/add_event.py +237 -237
  460. package/skills/sophnet-schedule/src/apps/check_reminders.py +112 -112
  461. package/skills/sophnet-schedule/src/apps/check_roc.py +246 -246
  462. package/skills/sophnet-schedule/src/apps/generate_daily_plan.py +342 -342
  463. package/skills/sophnet-schedule/src/apps/import_events.py +216 -216
  464. package/skills/sophnet-schedule/src/apps/monitor_calendar_changes.py +140 -140
  465. package/skills/sophnet-schedule/src/apps/register_tasks.py +169 -169
  466. package/skills/sophnet-schedule/src/apps/sync_roc_to_gcal.py +174 -174
  467. package/skills/sophnet-schedule/src/compat.py +66 -66
  468. package/skills/sophnet-schedule/src/config/reminder_rules.yaml +96 -96
  469. package/skills/sophnet-schedule/src/config/roc_events.yaml +44 -44
  470. package/skills/sophnet-schedule/src/config/settings.py +133 -133
  471. package/skills/sophnet-schedule/src/config/task_registry.yaml +92 -92
  472. package/skills/sophnet-schedule/src/docs/FRONTEND_INTEGRATION_GUIDE.md +437 -437
  473. package/skills/sophnet-schedule/src/gcal/client.py +374 -374
  474. package/skills/sophnet-schedule/src/gcal/models.py +91 -91
  475. package/skills/sophnet-schedule/src/requirements.txt +6 -6
  476. package/skills/sophnet-schedule/src/scripts/setup_gcal_token.py +85 -85
  477. package/skills/sophnet-schedule/src/server.py +669 -669
  478. package/skills/sophnet-schedule/src/services/calendar_backend.py +139 -139
  479. package/skills/sophnet-schedule/src/services/conflict_detector.py +96 -96
  480. package/skills/sophnet-schedule/src/services/datetime_utils.py +117 -117
  481. package/skills/sophnet-schedule/src/services/event_classifier.py +100 -100
  482. package/skills/sophnet-schedule/src/services/event_diff.py +160 -160
  483. package/skills/sophnet-schedule/src/services/google_integration.py +500 -500
  484. package/skills/sophnet-schedule/src/services/job_store.py +100 -100
  485. package/skills/sophnet-schedule/src/services/local_event_store.py +266 -266
  486. package/skills/sophnet-schedule/src/services/reminder_planner.py +116 -116
  487. package/skills/sophnet-schedule/src/services/runtime_utils.py +31 -31
  488. package/skills/sophnet-schedule/src/services/table_parser.py +286 -286
  489. package/skills/sophnet-schedule/src/services/task_builder.py +167 -167
  490. package/skills/sophnet-schedule/src/services/time_window.py +72 -72
  491. package/skills/sophnet-sticker-edit/skill.json +27 -27
  492. package/skills/sophnet-sticker-edit/src/SKILL.md +80 -80
  493. package/skills/sophnet-sticker-edit/src/pyproject.toml +9 -9
  494. package/skills/sophnet-sticker-edit/src/scripts/edit_sticker_image.py +403 -403
  495. package/skills/sophnet-stock/skill.json +20 -20
  496. package/skills/sophnet-stock/src/App-Plan.md +442 -442
  497. package/skills/sophnet-stock/src/README.md +214 -214
  498. package/skills/sophnet-stock/src/SKILL.md +236 -236
  499. package/skills/sophnet-stock/src/TODO.md +394 -394
  500. package/skills/sophnet-stock/src/_meta.json +5 -5
  501. package/skills/sophnet-stock/src/docs/ARCHITECTURE.md +408 -408
  502. package/skills/sophnet-stock/src/docs/CONCEPT.md +233 -233
  503. package/skills/sophnet-stock/src/docs/HOT_SCANNER.md +288 -288
  504. package/skills/sophnet-stock/src/docs/README.md +95 -95
  505. package/skills/sophnet-stock/src/docs/USAGE.md +465 -465
  506. package/skills/sophnet-stock/src/scripts/analyze_stock.py +2565 -2565
  507. package/skills/sophnet-stock/src/scripts/dividends.py +365 -365
  508. package/skills/sophnet-stock/src/scripts/hot_scanner.py +582 -582
  509. package/skills/sophnet-stock/src/scripts/portfolio.py +548 -548
  510. package/skills/sophnet-stock/src/scripts/rumor_scanner.py +342 -342
  511. package/skills/sophnet-stock/src/scripts/test_stock_analysis.py +409 -409
  512. package/skills/sophnet-stock/src/scripts/watchlist.py +336 -336
  513. package/skills/sophnet-training-install/skill.json +27 -27
  514. package/skills/sophnet-training-install/src/SKILL.md +211 -211
  515. package/skills/sophnet-training-install/src/pyproject.toml +6 -6
  516. package/skills/sophnet-training-install/src/scripts/backup_md.py +35 -35
  517. package/skills/sophnet-training-install/src/scripts/check_installed.py +144 -144
  518. package/skills/sophnet-training-install/src/scripts/update_config.py +142 -142
  519. package/skills/sophnet-training-install/src/scripts/update_md.py +73 -73
  520. package/skills/sophnet-tts/skill.json +20 -20
  521. package/skills/sophnet-tts/src/SKILL.md +79 -79
  522. package/skills/sophnet-tts/src/pyproject.toml +9 -9
  523. package/skills/sophnet-tts/src/scripts/gen_tts.py +130 -130
  524. package/skills/sophnet-video-generate/skill.json +37 -37
  525. package/skills/sophnet-video-generate/src/SKILL.md +117 -117
  526. package/skills/sophnet-video-generate/src/scripts/gen_video.py +321 -321
  527. package/skills/sophnet-xlsx/skill.json +20 -20
  528. package/skills/sophnet-xlsx/src/SKILL.md +399 -399
  529. package/skills/sophnet-xlsx/src/pyproject.toml +11 -11
  530. package/skills/sophnet-xlsx/src/scripts/ensure_uv_env.sh +68 -68
  531. package/skills/sophnet-xlsx/src/scripts/office/helpers/merge_runs.py +199 -199
  532. package/skills/sophnet-xlsx/src/scripts/office/helpers/simplify_redlines.py +197 -197
  533. package/skills/sophnet-xlsx/src/scripts/office/pack.py +159 -159
  534. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -1499
  535. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -146
  536. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -1085
  537. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -11
  538. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -3081
  539. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -23
  540. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -185
  541. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -287
  542. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -1676
  543. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -28
  544. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -144
  545. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -174
  546. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -25
  547. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -18
  548. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -59
  549. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -56
  550. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -195
  551. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -582
  552. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -25
  553. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -4439
  554. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -570
  555. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -509
  556. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -12
  557. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -108
  558. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -96
  559. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -3646
  560. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -116
  561. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -42
  562. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -50
  563. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -49
  564. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -33
  565. package/skills/sophnet-xlsx/src/scripts/office/schemas/mce/mc.xsd +75 -75
  566. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2010.xsd +560 -560
  567. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2012.xsd +67 -67
  568. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2018.xsd +14 -14
  569. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -20
  570. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -13
  571. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -4
  572. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -8
  573. package/skills/sophnet-xlsx/src/scripts/office/soffice.py +183 -183
  574. package/skills/sophnet-xlsx/src/scripts/office/unpack.py +132 -132
  575. package/skills/sophnet-xlsx/src/scripts/office/validate.py +111 -111
  576. package/skills/sophnet-xlsx/src/scripts/office/validators/__init__.py +15 -15
  577. package/skills/sophnet-xlsx/src/scripts/office/validators/base.py +847 -847
  578. package/skills/sophnet-xlsx/src/scripts/office/validators/docx.py +446 -446
  579. package/skills/sophnet-xlsx/src/scripts/office/validators/pptx.py +275 -275
  580. package/skills/sophnet-xlsx/src/scripts/office/validators/redlining.py +247 -247
  581. package/skills/sophnet-xlsx/src/scripts/recalc.py +184 -184
  582. package/skills/sophnet-xlsx/src/scripts/upload_file.sh +96 -96
  583. package/skills/sophnet-xlsx/src/uv.lock +319 -319
  584. package/skills/ui-ux-pro-max/skill.json +20 -20
  585. package/skills/ui-ux-pro-max/src/SKILL.md +377 -377
  586. package/skills/ui-ux-pro-max/src/data/icons.csv +101 -101
  587. package/skills/ui-ux-pro-max/src/data/react-performance.csv +45 -45
  588. package/skills/ui-ux-pro-max/src/data/stacks/astro.csv +54 -54
  589. package/skills/ui-ux-pro-max/src/data/stacks/jetpack-compose.csv +53 -53
  590. package/skills/ui-ux-pro-max/src/data/stacks/nuxt-ui.csv +51 -51
  591. package/skills/ui-ux-pro-max/src/data/stacks/nuxtjs.csv +59 -59
  592. package/skills/ui-ux-pro-max/src/data/stacks/shadcn.csv +61 -61
  593. package/skills/ui-ux-pro-max/src/data/typography.csv +57 -57
  594. package/skills/ui-ux-pro-max/src/data/ui-reasoning.csv +101 -101
  595. package/skills/ui-ux-pro-max/src/data/web-interface.csv +31 -31
  596. package/skills/ui-ux-pro-max/src/scripts/core.py +253 -253
  597. package/skills/ui-ux-pro-max/src/scripts/design_system.py +1067 -1067
  598. package/skills/video-understand/skill.json +20 -20
  599. package/skills/video-understand/src/SKILL.md +79 -79
  600. package/skills/video-understand/src/scripts/video_understand.py +204 -204
  601. package/skills/weather/skill.json +19 -19
  602. package/skills/weather/src/SKILL.md +112 -112
  603. package/skills/web-scraper/skill.json +20 -20
  604. package/skills/web-scraper/src/SKILL.md +101 -101
  605. package/skills/web-scraper/src/scripts/scrape.py +270 -270
  606. package/skills/website-builder/skill.json +20 -20
  607. package/skills/website-builder/src/SKILL.md +266 -266
  608. package/skills/website-builder/src/scripts/deploy_site.sh +46 -46
  609. package/skills/wechat-article-publisher/skill.json +20 -20
  610. package/skills/wechat-article-publisher/src/SKILL.md +60 -60
  611. package/skills/wechat-article-publisher/src/config.json +6 -6
  612. package/skills/wechat-article-publisher/src/pyproject.toml +12 -12
  613. package/skills/wechat-article-publisher/src/scripts/publish_wechat.py +825 -825
  614. package/skills/xiaohongshu/skill.json +20 -20
  615. package/skills/xiaohongshu/src/SKILL.md +91 -91
  616. package/skills/xiaohongshu/src/_meta.json +5 -5
  617. package/skills/xiaohongshu/src/assets/card.html +216 -216
  618. package/skills/xiaohongshu/src/assets/cover.html +82 -82
  619. package/skills/xiaohongshu/src/assets/example.md +84 -84
  620. package/skills/xiaohongshu/src/assets/styles.css +318 -318
  621. package/skills/xiaohongshu/src/scripts/render_xhs_v2.py +737 -737
  622. package/skills/xiaohongshu/src/scripts/sign_server.py +158 -158
  623. package/skills/xiaohongshu/src/scripts/stealth.min.js +6 -6
  624. package/skills/xiaohongshu/src/scripts/xhs_tool.py +186 -186
  625. package/skills/xiaohongshu/src/workflow.py +185 -185
  626. package/src/commands/agent.js +112 -112
  627. package/src/commands/download.js +101 -101
  628. package/src/commands/info.js +58 -58
  629. package/src/commands/list.js +71 -71
  630. package/src/utils/agents.js +36 -36
  631. package/src/utils/config.js +22 -22
  632. package/src/utils/paths.js +31 -31
  633. package/src/utils/versions.js +57 -57
@@ -1,679 +1,679 @@
1
- """Tests for bot_client_setup.py — setup/remove lifecycle."""
2
-
3
- import json
4
- import os
5
- import sys
6
- import tempfile
7
- import unittest
8
-
9
- sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))
10
-
11
- from bot_client_setup import (
12
- CREDENTIALS_FILENAME,
13
- SECTION_MARKER_END,
14
- SECTION_MARKER_START,
15
- SESSION_FILENAME,
16
- _find_section,
17
- generate_proxy_section,
18
- generate_section,
19
- remove,
20
- setup,
21
- )
22
-
23
-
24
- class TestGenerateSection(unittest.TestCase):
25
- """Test AGENTS.md section generation (raw mode)."""
26
-
27
- def test_contains_markers(self):
28
- section = generate_section(
29
- url="https://example.com/bot-api/chat",
30
- remote_agent_id="main",
31
- agent_id="vip-reader",
32
- credentials_path="data/bot_credentials.json",
33
- )
34
- self.assertIn(SECTION_MARKER_START, section)
35
- self.assertIn(SECTION_MARKER_END, section)
36
-
37
- def test_contains_credentials_path(self):
38
- section = generate_section(
39
- url="https://example.com/bot-api/chat",
40
- remote_agent_id="main",
41
- agent_id="vip-reader",
42
- credentials_path="data/bot_credentials.json",
43
- )
44
- self.assertIn("data/bot_credentials.json", section)
45
- self.assertNotIn("https://example.com/bot-api/chat", section)
46
-
47
- def test_contains_agent_ids(self):
48
- section = generate_section(
49
- url="https://example.com/bot-api/chat",
50
- remote_agent_id="custom-agent",
51
- agent_id="vip-reader",
52
- credentials_path="data/bot_credentials.json",
53
- )
54
- self.assertIn("custom-agent", section)
55
- self.assertIn("vip-reader", section)
56
-
57
- def test_contains_session_filename(self):
58
- section = generate_section(
59
- url="https://example.com/bot-api/chat",
60
- remote_agent_id="main",
61
- agent_id="vip-reader",
62
- credentials_path="data/bot_credentials.json",
63
- )
64
- self.assertIn(SESSION_FILENAME, section)
65
-
66
- def test_contains_error_handling(self):
67
- section = generate_section(
68
- url="https://example.com/bot-api/chat",
69
- remote_agent_id="main",
70
- agent_id="vip-reader",
71
- credentials_path="data/bot_credentials.json",
72
- )
73
- self.assertIn("错误处理", section)
74
- self.assertIn("401/403", section)
75
-
76
-
77
- class TestGenerateProxySection(unittest.TestCase):
78
- """Test AGENTS.md section generation (proxy mode)."""
79
-
80
- def test_contains_markers(self):
81
- section = generate_proxy_section(
82
- remote_agent_id="intern-helper",
83
- agent_id="test",
84
- wrapper_path="/path/to/bot_client_safe.sh",
85
- )
86
- self.assertIn(SECTION_MARKER_START, section)
87
- self.assertIn(SECTION_MARKER_END, section)
88
-
89
- def test_contains_wrapper_path(self):
90
- section = generate_proxy_section(
91
- remote_agent_id="intern-helper",
92
- agent_id="test",
93
- wrapper_path="/path/to/bot_client_safe.sh",
94
- )
95
- self.assertIn("/path/to/bot_client_safe.sh", section)
96
-
97
- def test_does_not_contain_curl_commands(self):
98
- """Proxy section should not contain executable curl/python3 commands."""
99
- section = generate_proxy_section(
100
- remote_agent_id="intern-helper",
101
- agent_id="test",
102
- wrapper_path="/path/to/bot_client_safe.sh",
103
- )
104
- self.assertNotIn("curl -s", section)
105
- self.assertNotIn("python3 -c", section)
106
- self.assertNotIn("BOT_URL=", section)
107
-
108
- def test_does_not_contain_credentials(self):
109
- section = generate_proxy_section(
110
- remote_agent_id="intern-helper",
111
- agent_id="test",
112
- wrapper_path="/path/to/bot_client_safe.sh",
113
- )
114
- self.assertNotIn("bot_credentials.json", section)
115
- self.assertNotIn("BOT_SECRET", section)
116
-
117
- def test_contains_agent_ids(self):
118
- section = generate_proxy_section(
119
- remote_agent_id="intern-helper",
120
- agent_id="test",
121
- wrapper_path="/path/to/bot_client_safe.sh",
122
- )
123
- self.assertIn("intern-helper", section)
124
- self.assertIn("test", section)
125
-
126
- def test_forbids_direct_access(self):
127
- section = generate_proxy_section(
128
- remote_agent_id="intern-helper",
129
- agent_id="test",
130
- wrapper_path="/path/to/bot_client_safe.sh",
131
- )
132
- self.assertIn("禁止", section)
133
- self.assertIn("系统强制执行", section)
134
-
135
-
136
- class TestFindSection(unittest.TestCase):
137
- """Test section boundary detection."""
138
-
139
- def test_no_section(self):
140
- self.assertIsNone(_find_section("# Hello\nSome content."))
141
-
142
- def test_finds_section(self):
143
- content = f"before\n{SECTION_MARKER_START}\nmiddle\n{SECTION_MARKER_END}\nafter"
144
- bounds = _find_section(content)
145
- self.assertIsNotNone(bounds)
146
- start, end = bounds
147
- extracted = content[start:end]
148
- self.assertTrue(extracted.startswith(SECTION_MARKER_START))
149
- self.assertTrue(extracted.endswith(SECTION_MARKER_END))
150
-
151
- def test_missing_end_marker(self):
152
- content = f"before\n{SECTION_MARKER_START}\nmiddle\n"
153
- self.assertIsNone(_find_section(content))
154
-
155
-
156
- class TestSetup(unittest.TestCase):
157
- """Test setup command (raw mode)."""
158
-
159
- def setUp(self):
160
- self.tmpdir = tempfile.mkdtemp()
161
- self.workspace_root = os.path.join(self.tmpdir, "workspaces")
162
- self.agent_id = "test-agent"
163
- self.workspace = os.path.join(self.workspace_root, self.agent_id)
164
- os.makedirs(self.workspace)
165
- self.agents_md = os.path.join(self.workspace, "AGENTS.md")
166
- with open(self.agents_md, "w") as f:
167
- f.write("# Test Agent\n\nExisting content.\n")
168
- os.makedirs(os.path.join(self.workspace, "data"))
169
-
170
- def tearDown(self):
171
- import shutil
172
- shutil.rmtree(self.tmpdir, ignore_errors=True)
173
-
174
- def test_setup_success(self):
175
- result = setup(
176
- agent_id=self.agent_id,
177
- url="https://example.com/bot-api/chat",
178
- secret="test-secret",
179
- remote_agent_id="main",
180
- workspace_root=self.workspace_root,
181
- )
182
- self.assertEqual(result["status"], "ok")
183
- self.assertEqual(result["agent_id"], self.agent_id)
184
- self.assertEqual(result["url"], "https://example.com/bot-api/chat")
185
-
186
- def test_agents_md_updated(self):
187
- setup(
188
- agent_id=self.agent_id,
189
- url="https://example.com/bot-api/chat",
190
- secret="test-secret",
191
- remote_agent_id="main",
192
- workspace_root=self.workspace_root,
193
- )
194
- with open(self.agents_md) as f:
195
- content = f.read()
196
- self.assertIn(SECTION_MARKER_START, content)
197
- self.assertIn(SECTION_MARKER_END, content)
198
- self.assertIn("Existing content.", content)
199
-
200
- def test_credentials_file_created(self):
201
- setup(
202
- agent_id=self.agent_id,
203
- url="https://example.com/bot-api/chat",
204
- secret="test-secret",
205
- remote_agent_id="main",
206
- workspace_root=self.workspace_root,
207
- )
208
- creds_path = os.path.join(self.workspace, "data", CREDENTIALS_FILENAME)
209
- self.assertTrue(os.path.isfile(creds_path))
210
- with open(creds_path) as f:
211
- creds = json.load(f)
212
- self.assertEqual(creds["url"], "https://example.com/bot-api/chat")
213
- self.assertEqual(creds["secret"], "test-secret")
214
- self.assertEqual(creds["remote_agent_id"], "main")
215
-
216
- def test_idempotent_setup(self):
217
- """Running setup twice should replace, not duplicate."""
218
- for _ in range(2):
219
- setup(
220
- agent_id=self.agent_id,
221
- url="https://example.com/bot-api/chat",
222
- secret="test-secret",
223
- remote_agent_id="main",
224
- workspace_root=self.workspace_root,
225
- )
226
- with open(self.agents_md) as f:
227
- content = f.read()
228
- self.assertEqual(content.count(SECTION_MARKER_START), 1)
229
- self.assertEqual(content.count(SECTION_MARKER_END), 1)
230
-
231
- def test_setup_updates_url(self):
232
- """Second setup with different URL should update."""
233
- setup(
234
- agent_id=self.agent_id,
235
- url="https://old.com/bot-api/chat",
236
- secret="old-secret",
237
- remote_agent_id="main",
238
- workspace_root=self.workspace_root,
239
- )
240
- setup(
241
- agent_id=self.agent_id,
242
- url="https://new.com/bot-api/chat",
243
- secret="new-secret",
244
- remote_agent_id="main",
245
- workspace_root=self.workspace_root,
246
- )
247
- creds_path = os.path.join(self.workspace, "data", CREDENTIALS_FILENAME)
248
- with open(creds_path) as f:
249
- creds = json.load(f)
250
- self.assertEqual(creds["url"], "https://new.com/bot-api/chat")
251
- self.assertEqual(creds["secret"], "new-secret")
252
-
253
- def test_workspace_not_found(self):
254
- result = setup(
255
- agent_id="nonexistent",
256
- url="https://example.com/bot-api/chat",
257
- secret="s",
258
- remote_agent_id="main",
259
- workspace_root=self.workspace_root,
260
- )
261
- self.assertEqual(result["status"], "error")
262
-
263
- def test_agents_md_not_found(self):
264
- os.remove(self.agents_md)
265
- result = setup(
266
- agent_id=self.agent_id,
267
- url="https://example.com/bot-api/chat",
268
- secret="s",
269
- remote_agent_id="main",
270
- workspace_root=self.workspace_root,
271
- )
272
- self.assertEqual(result["status"], "error")
273
-
274
- def test_data_dir_created_if_missing(self):
275
- """If data/ doesn't exist, setup should create it."""
276
- import shutil
277
- shutil.rmtree(os.path.join(self.workspace, "data"))
278
- result = setup(
279
- agent_id=self.agent_id,
280
- url="https://example.com/bot-api/chat",
281
- secret="s",
282
- remote_agent_id="main",
283
- workspace_root=self.workspace_root,
284
- )
285
- self.assertEqual(result["status"], "ok")
286
- self.assertTrue(os.path.isdir(os.path.join(self.workspace, "data")))
287
-
288
-
289
- class TestSetupProxy(unittest.TestCase):
290
- """Test setup command (proxy mode)."""
291
-
292
- def setUp(self):
293
- self.tmpdir = tempfile.mkdtemp()
294
- self.workspace_root = os.path.join(self.tmpdir, "workspaces")
295
- self.admin_dir = os.path.join(self.tmpdir, "admin")
296
- self.exec_approvals = os.path.join(self.tmpdir, "exec-approvals.json")
297
- self.agent_id = "proxy-test"
298
- self.workspace = os.path.join(self.workspace_root, self.agent_id)
299
- os.makedirs(self.workspace)
300
- self.agents_md = os.path.join(self.workspace, "AGENTS.md")
301
- with open(self.agents_md, "w") as f:
302
- f.write("# Proxy Test Agent\n\nExisting content.\n")
303
-
304
- def tearDown(self):
305
- import shutil
306
- shutil.rmtree(self.tmpdir, ignore_errors=True)
307
-
308
- def test_proxy_setup_success(self):
309
- result = setup(
310
- agent_id=self.agent_id,
311
- url="https://example.com/bot-api/chat",
312
- secret="proxy-secret",
313
- remote_agent_id="intern-helper",
314
- workspace_root=self.workspace_root,
315
- mode="proxy",
316
- admin_dir=self.admin_dir,
317
- exec_approvals_path=self.exec_approvals,
318
- )
319
- self.assertEqual(result["status"], "ok")
320
- self.assertEqual(result["mode"], "proxy")
321
- self.assertTrue(result["exec_allowlist_updated"])
322
-
323
- def test_proxy_credentials_in_admin_dir(self):
324
- """Proxy mode stores credentials in admin_dir, not workspace."""
325
- setup(
326
- agent_id=self.agent_id,
327
- url="https://example.com/bot-api/chat",
328
- secret="proxy-secret",
329
- remote_agent_id="intern-helper",
330
- workspace_root=self.workspace_root,
331
- mode="proxy",
332
- admin_dir=self.admin_dir,
333
- exec_approvals_path=self.exec_approvals,
334
- )
335
- admin_creds = os.path.join(self.admin_dir, self.agent_id, CREDENTIALS_FILENAME)
336
- self.assertTrue(os.path.isfile(admin_creds))
337
- with open(admin_creds) as f:
338
- creds = json.load(f)
339
- self.assertEqual(creds["url"], "https://example.com/bot-api/chat")
340
- self.assertEqual(creds["secret"], "proxy-secret")
341
-
342
- workspace_creds = os.path.join(self.workspace, "data", CREDENTIALS_FILENAME)
343
- self.assertFalse(os.path.exists(workspace_creds))
344
-
345
- def test_proxy_agents_md_no_curl_commands(self):
346
- """Proxy mode AGENTS.md should not contain executable curl/python3 commands."""
347
- setup(
348
- agent_id=self.agent_id,
349
- url="https://example.com/bot-api/chat",
350
- secret="proxy-secret",
351
- remote_agent_id="intern-helper",
352
- workspace_root=self.workspace_root,
353
- mode="proxy",
354
- admin_dir=self.admin_dir,
355
- exec_approvals_path=self.exec_approvals,
356
- )
357
- with open(self.agents_md) as f:
358
- content = f.read()
359
- self.assertIn(SECTION_MARKER_START, content)
360
- self.assertIn("bot_client_safe.sh", content)
361
- self.assertNotIn("curl -s", content)
362
- self.assertNotIn("python3 -c", content)
363
- self.assertNotIn("BOT_SECRET", content)
364
- self.assertNotIn("BOT_URL=", content)
365
-
366
- def test_proxy_agents_md_readonly(self):
367
- """Proxy mode should set AGENTS.md to read-only."""
368
- setup(
369
- agent_id=self.agent_id,
370
- url="https://example.com/bot-api/chat",
371
- secret="proxy-secret",
372
- remote_agent_id="intern-helper",
373
- workspace_root=self.workspace_root,
374
- mode="proxy",
375
- admin_dir=self.admin_dir,
376
- exec_approvals_path=self.exec_approvals,
377
- )
378
- mode = os.stat(self.agents_md).st_mode
379
- self.assertFalse(mode & 0o200, "AGENTS.md should be read-only")
380
-
381
- def test_proxy_exec_allowlist_updated(self):
382
- """Proxy mode should add bot_client_safe.sh to exec allowlist."""
383
- setup(
384
- agent_id=self.agent_id,
385
- url="https://example.com/bot-api/chat",
386
- secret="proxy-secret",
387
- remote_agent_id="intern-helper",
388
- workspace_root=self.workspace_root,
389
- mode="proxy",
390
- admin_dir=self.admin_dir,
391
- exec_approvals_path=self.exec_approvals,
392
- )
393
- self.assertTrue(os.path.isfile(self.exec_approvals))
394
- with open(self.exec_approvals) as f:
395
- data = json.load(f)
396
- agent_entry = data["agents"][self.agent_id]
397
- patterns = [e["pattern"] for e in agent_entry["allowlist"]]
398
- self.assertTrue(
399
- any("bot_client_safe.sh" in p for p in patterns),
400
- f"bot_client_safe.sh not in allowlist: {patterns}",
401
- )
402
-
403
- def test_proxy_idempotent(self):
404
- """Running proxy setup twice should not duplicate."""
405
- for _ in range(2):
406
- setup(
407
- agent_id=self.agent_id,
408
- url="https://example.com/bot-api/chat",
409
- secret="proxy-secret",
410
- remote_agent_id="intern-helper",
411
- workspace_root=self.workspace_root,
412
- mode="proxy",
413
- admin_dir=self.admin_dir,
414
- exec_approvals_path=self.exec_approvals,
415
- )
416
- with open(self.agents_md) as f:
417
- content = f.read()
418
- self.assertEqual(content.count(SECTION_MARKER_START), 1)
419
-
420
- with open(self.exec_approvals) as f:
421
- data = json.load(f)
422
- patterns = [e["pattern"] for e in data["agents"][self.agent_id]["allowlist"]]
423
- safe_sh_count = sum(1 for p in patterns if "bot_client_safe.sh" in p)
424
- self.assertEqual(safe_sh_count, 1)
425
-
426
- def test_proxy_handles_existing_readonly(self):
427
- """Proxy setup should handle already read-only AGENTS.md."""
428
- import stat as stat_mod
429
- os.chmod(self.agents_md, stat_mod.S_IRUSR | stat_mod.S_IRGRP | stat_mod.S_IROTH)
430
- result = setup(
431
- agent_id=self.agent_id,
432
- url="https://example.com/bot-api/chat",
433
- secret="proxy-secret",
434
- remote_agent_id="intern-helper",
435
- workspace_root=self.workspace_root,
436
- mode="proxy",
437
- admin_dir=self.admin_dir,
438
- exec_approvals_path=self.exec_approvals,
439
- )
440
- self.assertEqual(result["status"], "ok")
441
-
442
-
443
- class TestRemove(unittest.TestCase):
444
- """Test remove command."""
445
-
446
- def setUp(self):
447
- self.tmpdir = tempfile.mkdtemp()
448
- self.workspace_root = os.path.join(self.tmpdir, "workspaces")
449
- self.admin_dir = os.path.join(self.tmpdir, "admin")
450
- self.agent_id = "test-agent"
451
- self.workspace = os.path.join(self.workspace_root, self.agent_id)
452
- os.makedirs(os.path.join(self.workspace, "data"))
453
- self.agents_md = os.path.join(self.workspace, "AGENTS.md")
454
- with open(self.agents_md, "w") as f:
455
- f.write("# Test Agent\n\nExisting content.\n")
456
-
457
- def tearDown(self):
458
- import shutil
459
- shutil.rmtree(self.tmpdir, ignore_errors=True)
460
-
461
- def _setup_raw(self):
462
- setup(
463
- agent_id=self.agent_id,
464
- url="https://example.com/bot-api/chat",
465
- secret="test-secret",
466
- remote_agent_id="main",
467
- workspace_root=self.workspace_root,
468
- )
469
- session_path = os.path.join(self.workspace, "data", SESSION_FILENAME)
470
- with open(session_path, "w") as f:
471
- f.write("session-key-123")
472
-
473
- def test_remove_success(self):
474
- self._setup_raw()
475
- result = remove(
476
- agent_id=self.agent_id,
477
- workspace_root=self.workspace_root,
478
- admin_dir=self.admin_dir,
479
- )
480
- self.assertEqual(result["status"], "ok")
481
- self.assertTrue(result["removed_section"])
482
-
483
- def test_section_removed_from_agents_md(self):
484
- self._setup_raw()
485
- remove(
486
- agent_id=self.agent_id,
487
- workspace_root=self.workspace_root,
488
- admin_dir=self.admin_dir,
489
- )
490
- with open(self.agents_md) as f:
491
- content = f.read()
492
- self.assertNotIn(SECTION_MARKER_START, content)
493
- self.assertNotIn(SECTION_MARKER_END, content)
494
-
495
- def test_original_content_preserved(self):
496
- with open(self.agents_md, "w") as f:
497
- f.write("# Test Agent\n\nExisting content.\n")
498
- self._setup_raw()
499
- remove(
500
- agent_id=self.agent_id,
501
- workspace_root=self.workspace_root,
502
- admin_dir=self.admin_dir,
503
- )
504
- with open(self.agents_md) as f:
505
- content = f.read()
506
- self.assertIn("Existing content.", content)
507
-
508
- def test_credentials_and_session_deleted(self):
509
- self._setup_raw()
510
- result = remove(
511
- agent_id=self.agent_id,
512
- workspace_root=self.workspace_root,
513
- admin_dir=self.admin_dir,
514
- )
515
- self.assertIn(CREDENTIALS_FILENAME, result["removed_files"])
516
- self.assertIn(SESSION_FILENAME, result["removed_files"])
517
- self.assertFalse(
518
- os.path.isfile(os.path.join(self.workspace, "data", CREDENTIALS_FILENAME))
519
- )
520
- self.assertFalse(
521
- os.path.isfile(os.path.join(self.workspace, "data", SESSION_FILENAME))
522
- )
523
-
524
- def test_remove_no_section(self):
525
- with open(self.agents_md, "w") as f:
526
- f.write("# Test Agent\n")
527
- result = remove(
528
- agent_id=self.agent_id,
529
- workspace_root=self.workspace_root,
530
- admin_dir=self.admin_dir,
531
- )
532
- self.assertEqual(result["status"], "ok")
533
- self.assertFalse(result["removed_section"])
534
-
535
- def test_remove_workspace_not_found(self):
536
- result = remove(
537
- agent_id="nonexistent",
538
- workspace_root=self.workspace_root,
539
- admin_dir=self.admin_dir,
540
- )
541
- self.assertEqual(result["status"], "error")
542
-
543
-
544
- class TestRemoveProxy(unittest.TestCase):
545
- """Test remove command for proxy mode setups."""
546
-
547
- def setUp(self):
548
- self.tmpdir = tempfile.mkdtemp()
549
- self.workspace_root = os.path.join(self.tmpdir, "workspaces")
550
- self.admin_dir = os.path.join(self.tmpdir, "admin")
551
- self.exec_approvals = os.path.join(self.tmpdir, "exec-approvals.json")
552
- self.agent_id = "proxy-remove-test"
553
- self.workspace = os.path.join(self.workspace_root, self.agent_id)
554
- os.makedirs(self.workspace)
555
- with open(os.path.join(self.workspace, "AGENTS.md"), "w") as f:
556
- f.write("# Proxy Remove Test\n")
557
-
558
- def tearDown(self):
559
- import shutil
560
- shutil.rmtree(self.tmpdir, ignore_errors=True)
561
-
562
- def test_remove_proxy_credentials(self):
563
- """Remove should clean up admin_dir credentials."""
564
- setup(
565
- agent_id=self.agent_id,
566
- url="https://example.com/bot-api/chat",
567
- secret="s",
568
- remote_agent_id="main",
569
- workspace_root=self.workspace_root,
570
- mode="proxy",
571
- admin_dir=self.admin_dir,
572
- exec_approvals_path=self.exec_approvals,
573
- )
574
- admin_creds = os.path.join(self.admin_dir, self.agent_id, CREDENTIALS_FILENAME)
575
- self.assertTrue(os.path.isfile(admin_creds))
576
-
577
- result = remove(
578
- agent_id=self.agent_id,
579
- workspace_root=self.workspace_root,
580
- admin_dir=self.admin_dir,
581
- )
582
- self.assertEqual(result["status"], "ok")
583
- self.assertFalse(os.path.isfile(admin_creds))
584
- self.assertIn(CREDENTIALS_FILENAME, result["removed_files"])
585
-
586
-
587
- class TestCLI(unittest.TestCase):
588
- """Test command-line interface via subprocess."""
589
-
590
- def setUp(self):
591
- self.tmpdir = tempfile.mkdtemp()
592
- self.workspace_root = os.path.join(self.tmpdir, "workspaces")
593
- self.admin_dir = os.path.join(self.tmpdir, "admin")
594
- self.exec_approvals = os.path.join(self.tmpdir, "exec-approvals.json")
595
- self.agent_id = "cli-test"
596
- self.workspace = os.path.join(self.workspace_root, self.agent_id)
597
- os.makedirs(os.path.join(self.workspace, "data"))
598
- with open(os.path.join(self.workspace, "AGENTS.md"), "w") as f:
599
- f.write("# CLI Test Agent\n")
600
-
601
- def tearDown(self):
602
- import shutil
603
- shutil.rmtree(self.tmpdir, ignore_errors=True)
604
-
605
- def _run_cli(self, args: list[str]) -> tuple[dict, int]:
606
- import subprocess
607
- script = os.path.join(
608
- os.path.dirname(__file__), "..", "scripts", "bot_client_setup.py"
609
- )
610
- result = subprocess.run(
611
- [sys.executable, script, *args],
612
- capture_output=True,
613
- text=True,
614
- )
615
- output = json.loads(result.stdout) if result.stdout.strip() else {}
616
- return output, result.returncode
617
-
618
- def test_cli_setup_raw(self):
619
- output, rc = self._run_cli([
620
- "setup",
621
- "--agent-id", self.agent_id,
622
- "--url", "https://example.com/bot-api/chat",
623
- "--secret", "cli-secret",
624
- "--remote-agent-id", "main",
625
- "--workspace-root", self.workspace_root,
626
- ])
627
- self.assertEqual(rc, 0)
628
- self.assertEqual(output["status"], "ok")
629
-
630
- def test_cli_setup_proxy(self):
631
- output, rc = self._run_cli([
632
- "setup",
633
- "--agent-id", self.agent_id,
634
- "--url", "https://example.com/bot-api/chat",
635
- "--secret", "cli-secret",
636
- "--remote-agent-id", "intern-helper",
637
- "--mode", "proxy",
638
- "--workspace-root", self.workspace_root,
639
- "--admin-dir", self.admin_dir,
640
- "--exec-approvals-path", self.exec_approvals,
641
- ])
642
- self.assertEqual(rc, 0)
643
- self.assertEqual(output["status"], "ok")
644
- self.assertEqual(output["mode"], "proxy")
645
-
646
- def test_cli_remove(self):
647
- self._run_cli([
648
- "setup",
649
- "--agent-id", self.agent_id,
650
- "--url", "https://example.com/bot-api/chat",
651
- "--secret", "cli-secret",
652
- "--remote-agent-id", "main",
653
- "--workspace-root", self.workspace_root,
654
- ])
655
- output, rc = self._run_cli([
656
- "remove",
657
- "--agent-id", self.agent_id,
658
- "--workspace-root", self.workspace_root,
659
- "--admin-dir", self.admin_dir,
660
- ])
661
- self.assertEqual(rc, 0)
662
- self.assertEqual(output["status"], "ok")
663
- self.assertTrue(output["removed_section"])
664
-
665
- def test_cli_setup_missing_workspace(self):
666
- output, rc = self._run_cli([
667
- "setup",
668
- "--agent-id", "nonexistent",
669
- "--url", "https://example.com/bot-api/chat",
670
- "--secret", "s",
671
- "--remote-agent-id", "main",
672
- "--workspace-root", self.workspace_root,
673
- ])
674
- self.assertEqual(rc, 1)
675
- self.assertEqual(output["status"], "error")
676
-
677
-
678
- if __name__ == "__main__":
679
- unittest.main()
1
+ """Tests for bot_client_setup.py — setup/remove lifecycle."""
2
+
3
+ import json
4
+ import os
5
+ import sys
6
+ import tempfile
7
+ import unittest
8
+
9
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts"))
10
+
11
+ from bot_client_setup import (
12
+ CREDENTIALS_FILENAME,
13
+ SECTION_MARKER_END,
14
+ SECTION_MARKER_START,
15
+ SESSION_FILENAME,
16
+ _find_section,
17
+ generate_proxy_section,
18
+ generate_section,
19
+ remove,
20
+ setup,
21
+ )
22
+
23
+
24
+ class TestGenerateSection(unittest.TestCase):
25
+ """Test AGENTS.md section generation (raw mode)."""
26
+
27
+ def test_contains_markers(self):
28
+ section = generate_section(
29
+ url="https://example.com/bot-api/chat",
30
+ remote_agent_id="main",
31
+ agent_id="vip-reader",
32
+ credentials_path="data/bot_credentials.json",
33
+ )
34
+ self.assertIn(SECTION_MARKER_START, section)
35
+ self.assertIn(SECTION_MARKER_END, section)
36
+
37
+ def test_contains_credentials_path(self):
38
+ section = generate_section(
39
+ url="https://example.com/bot-api/chat",
40
+ remote_agent_id="main",
41
+ agent_id="vip-reader",
42
+ credentials_path="data/bot_credentials.json",
43
+ )
44
+ self.assertIn("data/bot_credentials.json", section)
45
+ self.assertNotIn("https://example.com/bot-api/chat", section)
46
+
47
+ def test_contains_agent_ids(self):
48
+ section = generate_section(
49
+ url="https://example.com/bot-api/chat",
50
+ remote_agent_id="custom-agent",
51
+ agent_id="vip-reader",
52
+ credentials_path="data/bot_credentials.json",
53
+ )
54
+ self.assertIn("custom-agent", section)
55
+ self.assertIn("vip-reader", section)
56
+
57
+ def test_contains_session_filename(self):
58
+ section = generate_section(
59
+ url="https://example.com/bot-api/chat",
60
+ remote_agent_id="main",
61
+ agent_id="vip-reader",
62
+ credentials_path="data/bot_credentials.json",
63
+ )
64
+ self.assertIn(SESSION_FILENAME, section)
65
+
66
+ def test_contains_error_handling(self):
67
+ section = generate_section(
68
+ url="https://example.com/bot-api/chat",
69
+ remote_agent_id="main",
70
+ agent_id="vip-reader",
71
+ credentials_path="data/bot_credentials.json",
72
+ )
73
+ self.assertIn("错误处理", section)
74
+ self.assertIn("401/403", section)
75
+
76
+
77
+ class TestGenerateProxySection(unittest.TestCase):
78
+ """Test AGENTS.md section generation (proxy mode)."""
79
+
80
+ def test_contains_markers(self):
81
+ section = generate_proxy_section(
82
+ remote_agent_id="intern-helper",
83
+ agent_id="test",
84
+ wrapper_path="/path/to/bot_client_safe.sh",
85
+ )
86
+ self.assertIn(SECTION_MARKER_START, section)
87
+ self.assertIn(SECTION_MARKER_END, section)
88
+
89
+ def test_contains_wrapper_path(self):
90
+ section = generate_proxy_section(
91
+ remote_agent_id="intern-helper",
92
+ agent_id="test",
93
+ wrapper_path="/path/to/bot_client_safe.sh",
94
+ )
95
+ self.assertIn("/path/to/bot_client_safe.sh", section)
96
+
97
+ def test_does_not_contain_curl_commands(self):
98
+ """Proxy section should not contain executable curl/python3 commands."""
99
+ section = generate_proxy_section(
100
+ remote_agent_id="intern-helper",
101
+ agent_id="test",
102
+ wrapper_path="/path/to/bot_client_safe.sh",
103
+ )
104
+ self.assertNotIn("curl -s", section)
105
+ self.assertNotIn("python3 -c", section)
106
+ self.assertNotIn("BOT_URL=", section)
107
+
108
+ def test_does_not_contain_credentials(self):
109
+ section = generate_proxy_section(
110
+ remote_agent_id="intern-helper",
111
+ agent_id="test",
112
+ wrapper_path="/path/to/bot_client_safe.sh",
113
+ )
114
+ self.assertNotIn("bot_credentials.json", section)
115
+ self.assertNotIn("BOT_SECRET", section)
116
+
117
+ def test_contains_agent_ids(self):
118
+ section = generate_proxy_section(
119
+ remote_agent_id="intern-helper",
120
+ agent_id="test",
121
+ wrapper_path="/path/to/bot_client_safe.sh",
122
+ )
123
+ self.assertIn("intern-helper", section)
124
+ self.assertIn("test", section)
125
+
126
+ def test_forbids_direct_access(self):
127
+ section = generate_proxy_section(
128
+ remote_agent_id="intern-helper",
129
+ agent_id="test",
130
+ wrapper_path="/path/to/bot_client_safe.sh",
131
+ )
132
+ self.assertIn("禁止", section)
133
+ self.assertIn("系统强制执行", section)
134
+
135
+
136
+ class TestFindSection(unittest.TestCase):
137
+ """Test section boundary detection."""
138
+
139
+ def test_no_section(self):
140
+ self.assertIsNone(_find_section("# Hello\nSome content."))
141
+
142
+ def test_finds_section(self):
143
+ content = f"before\n{SECTION_MARKER_START}\nmiddle\n{SECTION_MARKER_END}\nafter"
144
+ bounds = _find_section(content)
145
+ self.assertIsNotNone(bounds)
146
+ start, end = bounds
147
+ extracted = content[start:end]
148
+ self.assertTrue(extracted.startswith(SECTION_MARKER_START))
149
+ self.assertTrue(extracted.endswith(SECTION_MARKER_END))
150
+
151
+ def test_missing_end_marker(self):
152
+ content = f"before\n{SECTION_MARKER_START}\nmiddle\n"
153
+ self.assertIsNone(_find_section(content))
154
+
155
+
156
+ class TestSetup(unittest.TestCase):
157
+ """Test setup command (raw mode)."""
158
+
159
+ def setUp(self):
160
+ self.tmpdir = tempfile.mkdtemp()
161
+ self.workspace_root = os.path.join(self.tmpdir, "workspaces")
162
+ self.agent_id = "test-agent"
163
+ self.workspace = os.path.join(self.workspace_root, self.agent_id)
164
+ os.makedirs(self.workspace)
165
+ self.agents_md = os.path.join(self.workspace, "AGENTS.md")
166
+ with open(self.agents_md, "w") as f:
167
+ f.write("# Test Agent\n\nExisting content.\n")
168
+ os.makedirs(os.path.join(self.workspace, "data"))
169
+
170
+ def tearDown(self):
171
+ import shutil
172
+ shutil.rmtree(self.tmpdir, ignore_errors=True)
173
+
174
+ def test_setup_success(self):
175
+ result = setup(
176
+ agent_id=self.agent_id,
177
+ url="https://example.com/bot-api/chat",
178
+ secret="test-secret",
179
+ remote_agent_id="main",
180
+ workspace_root=self.workspace_root,
181
+ )
182
+ self.assertEqual(result["status"], "ok")
183
+ self.assertEqual(result["agent_id"], self.agent_id)
184
+ self.assertEqual(result["url"], "https://example.com/bot-api/chat")
185
+
186
+ def test_agents_md_updated(self):
187
+ setup(
188
+ agent_id=self.agent_id,
189
+ url="https://example.com/bot-api/chat",
190
+ secret="test-secret",
191
+ remote_agent_id="main",
192
+ workspace_root=self.workspace_root,
193
+ )
194
+ with open(self.agents_md) as f:
195
+ content = f.read()
196
+ self.assertIn(SECTION_MARKER_START, content)
197
+ self.assertIn(SECTION_MARKER_END, content)
198
+ self.assertIn("Existing content.", content)
199
+
200
+ def test_credentials_file_created(self):
201
+ setup(
202
+ agent_id=self.agent_id,
203
+ url="https://example.com/bot-api/chat",
204
+ secret="test-secret",
205
+ remote_agent_id="main",
206
+ workspace_root=self.workspace_root,
207
+ )
208
+ creds_path = os.path.join(self.workspace, "data", CREDENTIALS_FILENAME)
209
+ self.assertTrue(os.path.isfile(creds_path))
210
+ with open(creds_path) as f:
211
+ creds = json.load(f)
212
+ self.assertEqual(creds["url"], "https://example.com/bot-api/chat")
213
+ self.assertEqual(creds["secret"], "test-secret")
214
+ self.assertEqual(creds["remote_agent_id"], "main")
215
+
216
+ def test_idempotent_setup(self):
217
+ """Running setup twice should replace, not duplicate."""
218
+ for _ in range(2):
219
+ setup(
220
+ agent_id=self.agent_id,
221
+ url="https://example.com/bot-api/chat",
222
+ secret="test-secret",
223
+ remote_agent_id="main",
224
+ workspace_root=self.workspace_root,
225
+ )
226
+ with open(self.agents_md) as f:
227
+ content = f.read()
228
+ self.assertEqual(content.count(SECTION_MARKER_START), 1)
229
+ self.assertEqual(content.count(SECTION_MARKER_END), 1)
230
+
231
+ def test_setup_updates_url(self):
232
+ """Second setup with different URL should update."""
233
+ setup(
234
+ agent_id=self.agent_id,
235
+ url="https://old.com/bot-api/chat",
236
+ secret="old-secret",
237
+ remote_agent_id="main",
238
+ workspace_root=self.workspace_root,
239
+ )
240
+ setup(
241
+ agent_id=self.agent_id,
242
+ url="https://new.com/bot-api/chat",
243
+ secret="new-secret",
244
+ remote_agent_id="main",
245
+ workspace_root=self.workspace_root,
246
+ )
247
+ creds_path = os.path.join(self.workspace, "data", CREDENTIALS_FILENAME)
248
+ with open(creds_path) as f:
249
+ creds = json.load(f)
250
+ self.assertEqual(creds["url"], "https://new.com/bot-api/chat")
251
+ self.assertEqual(creds["secret"], "new-secret")
252
+
253
+ def test_workspace_not_found(self):
254
+ result = setup(
255
+ agent_id="nonexistent",
256
+ url="https://example.com/bot-api/chat",
257
+ secret="s",
258
+ remote_agent_id="main",
259
+ workspace_root=self.workspace_root,
260
+ )
261
+ self.assertEqual(result["status"], "error")
262
+
263
+ def test_agents_md_not_found(self):
264
+ os.remove(self.agents_md)
265
+ result = setup(
266
+ agent_id=self.agent_id,
267
+ url="https://example.com/bot-api/chat",
268
+ secret="s",
269
+ remote_agent_id="main",
270
+ workspace_root=self.workspace_root,
271
+ )
272
+ self.assertEqual(result["status"], "error")
273
+
274
+ def test_data_dir_created_if_missing(self):
275
+ """If data/ doesn't exist, setup should create it."""
276
+ import shutil
277
+ shutil.rmtree(os.path.join(self.workspace, "data"))
278
+ result = setup(
279
+ agent_id=self.agent_id,
280
+ url="https://example.com/bot-api/chat",
281
+ secret="s",
282
+ remote_agent_id="main",
283
+ workspace_root=self.workspace_root,
284
+ )
285
+ self.assertEqual(result["status"], "ok")
286
+ self.assertTrue(os.path.isdir(os.path.join(self.workspace, "data")))
287
+
288
+
289
+ class TestSetupProxy(unittest.TestCase):
290
+ """Test setup command (proxy mode)."""
291
+
292
+ def setUp(self):
293
+ self.tmpdir = tempfile.mkdtemp()
294
+ self.workspace_root = os.path.join(self.tmpdir, "workspaces")
295
+ self.admin_dir = os.path.join(self.tmpdir, "admin")
296
+ self.exec_approvals = os.path.join(self.tmpdir, "exec-approvals.json")
297
+ self.agent_id = "proxy-test"
298
+ self.workspace = os.path.join(self.workspace_root, self.agent_id)
299
+ os.makedirs(self.workspace)
300
+ self.agents_md = os.path.join(self.workspace, "AGENTS.md")
301
+ with open(self.agents_md, "w") as f:
302
+ f.write("# Proxy Test Agent\n\nExisting content.\n")
303
+
304
+ def tearDown(self):
305
+ import shutil
306
+ shutil.rmtree(self.tmpdir, ignore_errors=True)
307
+
308
+ def test_proxy_setup_success(self):
309
+ result = setup(
310
+ agent_id=self.agent_id,
311
+ url="https://example.com/bot-api/chat",
312
+ secret="proxy-secret",
313
+ remote_agent_id="intern-helper",
314
+ workspace_root=self.workspace_root,
315
+ mode="proxy",
316
+ admin_dir=self.admin_dir,
317
+ exec_approvals_path=self.exec_approvals,
318
+ )
319
+ self.assertEqual(result["status"], "ok")
320
+ self.assertEqual(result["mode"], "proxy")
321
+ self.assertTrue(result["exec_allowlist_updated"])
322
+
323
+ def test_proxy_credentials_in_admin_dir(self):
324
+ """Proxy mode stores credentials in admin_dir, not workspace."""
325
+ setup(
326
+ agent_id=self.agent_id,
327
+ url="https://example.com/bot-api/chat",
328
+ secret="proxy-secret",
329
+ remote_agent_id="intern-helper",
330
+ workspace_root=self.workspace_root,
331
+ mode="proxy",
332
+ admin_dir=self.admin_dir,
333
+ exec_approvals_path=self.exec_approvals,
334
+ )
335
+ admin_creds = os.path.join(self.admin_dir, self.agent_id, CREDENTIALS_FILENAME)
336
+ self.assertTrue(os.path.isfile(admin_creds))
337
+ with open(admin_creds) as f:
338
+ creds = json.load(f)
339
+ self.assertEqual(creds["url"], "https://example.com/bot-api/chat")
340
+ self.assertEqual(creds["secret"], "proxy-secret")
341
+
342
+ workspace_creds = os.path.join(self.workspace, "data", CREDENTIALS_FILENAME)
343
+ self.assertFalse(os.path.exists(workspace_creds))
344
+
345
+ def test_proxy_agents_md_no_curl_commands(self):
346
+ """Proxy mode AGENTS.md should not contain executable curl/python3 commands."""
347
+ setup(
348
+ agent_id=self.agent_id,
349
+ url="https://example.com/bot-api/chat",
350
+ secret="proxy-secret",
351
+ remote_agent_id="intern-helper",
352
+ workspace_root=self.workspace_root,
353
+ mode="proxy",
354
+ admin_dir=self.admin_dir,
355
+ exec_approvals_path=self.exec_approvals,
356
+ )
357
+ with open(self.agents_md) as f:
358
+ content = f.read()
359
+ self.assertIn(SECTION_MARKER_START, content)
360
+ self.assertIn("bot_client_safe.sh", content)
361
+ self.assertNotIn("curl -s", content)
362
+ self.assertNotIn("python3 -c", content)
363
+ self.assertNotIn("BOT_SECRET", content)
364
+ self.assertNotIn("BOT_URL=", content)
365
+
366
+ def test_proxy_agents_md_readonly(self):
367
+ """Proxy mode should set AGENTS.md to read-only."""
368
+ setup(
369
+ agent_id=self.agent_id,
370
+ url="https://example.com/bot-api/chat",
371
+ secret="proxy-secret",
372
+ remote_agent_id="intern-helper",
373
+ workspace_root=self.workspace_root,
374
+ mode="proxy",
375
+ admin_dir=self.admin_dir,
376
+ exec_approvals_path=self.exec_approvals,
377
+ )
378
+ mode = os.stat(self.agents_md).st_mode
379
+ self.assertFalse(mode & 0o200, "AGENTS.md should be read-only")
380
+
381
+ def test_proxy_exec_allowlist_updated(self):
382
+ """Proxy mode should add bot_client_safe.sh to exec allowlist."""
383
+ setup(
384
+ agent_id=self.agent_id,
385
+ url="https://example.com/bot-api/chat",
386
+ secret="proxy-secret",
387
+ remote_agent_id="intern-helper",
388
+ workspace_root=self.workspace_root,
389
+ mode="proxy",
390
+ admin_dir=self.admin_dir,
391
+ exec_approvals_path=self.exec_approvals,
392
+ )
393
+ self.assertTrue(os.path.isfile(self.exec_approvals))
394
+ with open(self.exec_approvals) as f:
395
+ data = json.load(f)
396
+ agent_entry = data["agents"][self.agent_id]
397
+ patterns = [e["pattern"] for e in agent_entry["allowlist"]]
398
+ self.assertTrue(
399
+ any("bot_client_safe.sh" in p for p in patterns),
400
+ f"bot_client_safe.sh not in allowlist: {patterns}",
401
+ )
402
+
403
+ def test_proxy_idempotent(self):
404
+ """Running proxy setup twice should not duplicate."""
405
+ for _ in range(2):
406
+ setup(
407
+ agent_id=self.agent_id,
408
+ url="https://example.com/bot-api/chat",
409
+ secret="proxy-secret",
410
+ remote_agent_id="intern-helper",
411
+ workspace_root=self.workspace_root,
412
+ mode="proxy",
413
+ admin_dir=self.admin_dir,
414
+ exec_approvals_path=self.exec_approvals,
415
+ )
416
+ with open(self.agents_md) as f:
417
+ content = f.read()
418
+ self.assertEqual(content.count(SECTION_MARKER_START), 1)
419
+
420
+ with open(self.exec_approvals) as f:
421
+ data = json.load(f)
422
+ patterns = [e["pattern"] for e in data["agents"][self.agent_id]["allowlist"]]
423
+ safe_sh_count = sum(1 for p in patterns if "bot_client_safe.sh" in p)
424
+ self.assertEqual(safe_sh_count, 1)
425
+
426
+ def test_proxy_handles_existing_readonly(self):
427
+ """Proxy setup should handle already read-only AGENTS.md."""
428
+ import stat as stat_mod
429
+ os.chmod(self.agents_md, stat_mod.S_IRUSR | stat_mod.S_IRGRP | stat_mod.S_IROTH)
430
+ result = setup(
431
+ agent_id=self.agent_id,
432
+ url="https://example.com/bot-api/chat",
433
+ secret="proxy-secret",
434
+ remote_agent_id="intern-helper",
435
+ workspace_root=self.workspace_root,
436
+ mode="proxy",
437
+ admin_dir=self.admin_dir,
438
+ exec_approvals_path=self.exec_approvals,
439
+ )
440
+ self.assertEqual(result["status"], "ok")
441
+
442
+
443
+ class TestRemove(unittest.TestCase):
444
+ """Test remove command."""
445
+
446
+ def setUp(self):
447
+ self.tmpdir = tempfile.mkdtemp()
448
+ self.workspace_root = os.path.join(self.tmpdir, "workspaces")
449
+ self.admin_dir = os.path.join(self.tmpdir, "admin")
450
+ self.agent_id = "test-agent"
451
+ self.workspace = os.path.join(self.workspace_root, self.agent_id)
452
+ os.makedirs(os.path.join(self.workspace, "data"))
453
+ self.agents_md = os.path.join(self.workspace, "AGENTS.md")
454
+ with open(self.agents_md, "w") as f:
455
+ f.write("# Test Agent\n\nExisting content.\n")
456
+
457
+ def tearDown(self):
458
+ import shutil
459
+ shutil.rmtree(self.tmpdir, ignore_errors=True)
460
+
461
+ def _setup_raw(self):
462
+ setup(
463
+ agent_id=self.agent_id,
464
+ url="https://example.com/bot-api/chat",
465
+ secret="test-secret",
466
+ remote_agent_id="main",
467
+ workspace_root=self.workspace_root,
468
+ )
469
+ session_path = os.path.join(self.workspace, "data", SESSION_FILENAME)
470
+ with open(session_path, "w") as f:
471
+ f.write("session-key-123")
472
+
473
+ def test_remove_success(self):
474
+ self._setup_raw()
475
+ result = remove(
476
+ agent_id=self.agent_id,
477
+ workspace_root=self.workspace_root,
478
+ admin_dir=self.admin_dir,
479
+ )
480
+ self.assertEqual(result["status"], "ok")
481
+ self.assertTrue(result["removed_section"])
482
+
483
+ def test_section_removed_from_agents_md(self):
484
+ self._setup_raw()
485
+ remove(
486
+ agent_id=self.agent_id,
487
+ workspace_root=self.workspace_root,
488
+ admin_dir=self.admin_dir,
489
+ )
490
+ with open(self.agents_md) as f:
491
+ content = f.read()
492
+ self.assertNotIn(SECTION_MARKER_START, content)
493
+ self.assertNotIn(SECTION_MARKER_END, content)
494
+
495
+ def test_original_content_preserved(self):
496
+ with open(self.agents_md, "w") as f:
497
+ f.write("# Test Agent\n\nExisting content.\n")
498
+ self._setup_raw()
499
+ remove(
500
+ agent_id=self.agent_id,
501
+ workspace_root=self.workspace_root,
502
+ admin_dir=self.admin_dir,
503
+ )
504
+ with open(self.agents_md) as f:
505
+ content = f.read()
506
+ self.assertIn("Existing content.", content)
507
+
508
+ def test_credentials_and_session_deleted(self):
509
+ self._setup_raw()
510
+ result = remove(
511
+ agent_id=self.agent_id,
512
+ workspace_root=self.workspace_root,
513
+ admin_dir=self.admin_dir,
514
+ )
515
+ self.assertIn(CREDENTIALS_FILENAME, result["removed_files"])
516
+ self.assertIn(SESSION_FILENAME, result["removed_files"])
517
+ self.assertFalse(
518
+ os.path.isfile(os.path.join(self.workspace, "data", CREDENTIALS_FILENAME))
519
+ )
520
+ self.assertFalse(
521
+ os.path.isfile(os.path.join(self.workspace, "data", SESSION_FILENAME))
522
+ )
523
+
524
+ def test_remove_no_section(self):
525
+ with open(self.agents_md, "w") as f:
526
+ f.write("# Test Agent\n")
527
+ result = remove(
528
+ agent_id=self.agent_id,
529
+ workspace_root=self.workspace_root,
530
+ admin_dir=self.admin_dir,
531
+ )
532
+ self.assertEqual(result["status"], "ok")
533
+ self.assertFalse(result["removed_section"])
534
+
535
+ def test_remove_workspace_not_found(self):
536
+ result = remove(
537
+ agent_id="nonexistent",
538
+ workspace_root=self.workspace_root,
539
+ admin_dir=self.admin_dir,
540
+ )
541
+ self.assertEqual(result["status"], "error")
542
+
543
+
544
+ class TestRemoveProxy(unittest.TestCase):
545
+ """Test remove command for proxy mode setups."""
546
+
547
+ def setUp(self):
548
+ self.tmpdir = tempfile.mkdtemp()
549
+ self.workspace_root = os.path.join(self.tmpdir, "workspaces")
550
+ self.admin_dir = os.path.join(self.tmpdir, "admin")
551
+ self.exec_approvals = os.path.join(self.tmpdir, "exec-approvals.json")
552
+ self.agent_id = "proxy-remove-test"
553
+ self.workspace = os.path.join(self.workspace_root, self.agent_id)
554
+ os.makedirs(self.workspace)
555
+ with open(os.path.join(self.workspace, "AGENTS.md"), "w") as f:
556
+ f.write("# Proxy Remove Test\n")
557
+
558
+ def tearDown(self):
559
+ import shutil
560
+ shutil.rmtree(self.tmpdir, ignore_errors=True)
561
+
562
+ def test_remove_proxy_credentials(self):
563
+ """Remove should clean up admin_dir credentials."""
564
+ setup(
565
+ agent_id=self.agent_id,
566
+ url="https://example.com/bot-api/chat",
567
+ secret="s",
568
+ remote_agent_id="main",
569
+ workspace_root=self.workspace_root,
570
+ mode="proxy",
571
+ admin_dir=self.admin_dir,
572
+ exec_approvals_path=self.exec_approvals,
573
+ )
574
+ admin_creds = os.path.join(self.admin_dir, self.agent_id, CREDENTIALS_FILENAME)
575
+ self.assertTrue(os.path.isfile(admin_creds))
576
+
577
+ result = remove(
578
+ agent_id=self.agent_id,
579
+ workspace_root=self.workspace_root,
580
+ admin_dir=self.admin_dir,
581
+ )
582
+ self.assertEqual(result["status"], "ok")
583
+ self.assertFalse(os.path.isfile(admin_creds))
584
+ self.assertIn(CREDENTIALS_FILENAME, result["removed_files"])
585
+
586
+
587
+ class TestCLI(unittest.TestCase):
588
+ """Test command-line interface via subprocess."""
589
+
590
+ def setUp(self):
591
+ self.tmpdir = tempfile.mkdtemp()
592
+ self.workspace_root = os.path.join(self.tmpdir, "workspaces")
593
+ self.admin_dir = os.path.join(self.tmpdir, "admin")
594
+ self.exec_approvals = os.path.join(self.tmpdir, "exec-approvals.json")
595
+ self.agent_id = "cli-test"
596
+ self.workspace = os.path.join(self.workspace_root, self.agent_id)
597
+ os.makedirs(os.path.join(self.workspace, "data"))
598
+ with open(os.path.join(self.workspace, "AGENTS.md"), "w") as f:
599
+ f.write("# CLI Test Agent\n")
600
+
601
+ def tearDown(self):
602
+ import shutil
603
+ shutil.rmtree(self.tmpdir, ignore_errors=True)
604
+
605
+ def _run_cli(self, args: list[str]) -> tuple[dict, int]:
606
+ import subprocess
607
+ script = os.path.join(
608
+ os.path.dirname(__file__), "..", "scripts", "bot_client_setup.py"
609
+ )
610
+ result = subprocess.run(
611
+ [sys.executable, script, *args],
612
+ capture_output=True,
613
+ text=True,
614
+ )
615
+ output = json.loads(result.stdout) if result.stdout.strip() else {}
616
+ return output, result.returncode
617
+
618
+ def test_cli_setup_raw(self):
619
+ output, rc = self._run_cli([
620
+ "setup",
621
+ "--agent-id", self.agent_id,
622
+ "--url", "https://example.com/bot-api/chat",
623
+ "--secret", "cli-secret",
624
+ "--remote-agent-id", "main",
625
+ "--workspace-root", self.workspace_root,
626
+ ])
627
+ self.assertEqual(rc, 0)
628
+ self.assertEqual(output["status"], "ok")
629
+
630
+ def test_cli_setup_proxy(self):
631
+ output, rc = self._run_cli([
632
+ "setup",
633
+ "--agent-id", self.agent_id,
634
+ "--url", "https://example.com/bot-api/chat",
635
+ "--secret", "cli-secret",
636
+ "--remote-agent-id", "intern-helper",
637
+ "--mode", "proxy",
638
+ "--workspace-root", self.workspace_root,
639
+ "--admin-dir", self.admin_dir,
640
+ "--exec-approvals-path", self.exec_approvals,
641
+ ])
642
+ self.assertEqual(rc, 0)
643
+ self.assertEqual(output["status"], "ok")
644
+ self.assertEqual(output["mode"], "proxy")
645
+
646
+ def test_cli_remove(self):
647
+ self._run_cli([
648
+ "setup",
649
+ "--agent-id", self.agent_id,
650
+ "--url", "https://example.com/bot-api/chat",
651
+ "--secret", "cli-secret",
652
+ "--remote-agent-id", "main",
653
+ "--workspace-root", self.workspace_root,
654
+ ])
655
+ output, rc = self._run_cli([
656
+ "remove",
657
+ "--agent-id", self.agent_id,
658
+ "--workspace-root", self.workspace_root,
659
+ "--admin-dir", self.admin_dir,
660
+ ])
661
+ self.assertEqual(rc, 0)
662
+ self.assertEqual(output["status"], "ok")
663
+ self.assertTrue(output["removed_section"])
664
+
665
+ def test_cli_setup_missing_workspace(self):
666
+ output, rc = self._run_cli([
667
+ "setup",
668
+ "--agent-id", "nonexistent",
669
+ "--url", "https://example.com/bot-api/chat",
670
+ "--secret", "s",
671
+ "--remote-agent-id", "main",
672
+ "--workspace-root", self.workspace_root,
673
+ ])
674
+ self.assertEqual(rc, 1)
675
+ self.assertEqual(output["status"], "error")
676
+
677
+
678
+ if __name__ == "__main__":
679
+ unittest.main()