@ngocsangairvds/vsaf 3.1.27 → 3.2.1

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 (303) hide show
  1. package/package.json +2 -2
  2. package/src/global.js +70 -10
  3. package/tools/skills/vds-scripts-skill/.openskills.json +6 -0
  4. package/tools/skills/vds-scripts-skill/QUALITY.md +44 -0
  5. package/tools/skills/vds-scripts-skill/SKILL.md +135 -0
  6. package/tools/skills/vds-scripts-skill/references/audit-commands.md +171 -0
  7. package/tools/skills/vds-scripts-skill/references/capability-index.md +34 -0
  8. package/tools/skills/vds-scripts-skill/references/development-commands.md +12 -0
  9. package/tools/skills/vds-scripts-skill/references/google-sheets.md +73 -0
  10. package/tools/skills/vds-scripts-skill/references/integration-commands.md +17 -0
  11. package/tools/skills/vds-scripts-skill/references/platform-bootstrap.md +31 -0
  12. package/tools/skills/vds-scripts-skill/references/specialist-routing.md +14 -0
  13. package/tools/skills/vds-scripts-skill/references/validation-commands.md +15 -0
  14. package/tools/skills/vsaf-build/SKILL.md +32 -2
  15. package/tools/skills/vsaf-ship/SKILL.md +41 -10
  16. package/tools/skills/vsaf-test/SKILL.md +8 -0
  17. package/tools/vds-scripts/.mcp.json +11 -0
  18. package/tools/vds-scripts/.secrets.baseline +133 -0
  19. package/tools/vds-scripts/AGENTS.md +152 -0
  20. package/tools/vds-scripts/CLAUDE.md +101 -0
  21. package/tools/vds-scripts/CLI_COMMAND_OPTIMIZATION.md +156 -0
  22. package/tools/vds-scripts/PACKAGE_P125B_IMPLEMENTATION_SUMMARY.md +131 -0
  23. package/tools/vds-scripts/PROJECT_COMPLETION_SUMMARY.md +45 -0
  24. package/tools/vds-scripts/README.md +97 -0
  25. package/tools/vds-scripts/bitbucket_manifest_mapping.toml +34 -0
  26. package/tools/vds-scripts/bitbucket_orchestrator/ARCHITECTURE_ANALYSIS.md +258 -0
  27. package/tools/vds-scripts/bitbucket_orchestrator/BITBUCKET_API_PRACTICES.md +393 -0
  28. package/tools/vds-scripts/bitbucket_orchestrator/EVALUATION_REPORT.md +61 -0
  29. package/tools/vds-scripts/bitbucket_orchestrator/FEATURES.md +908 -0
  30. package/tools/vds-scripts/bitbucket_orchestrator/README.md +687 -0
  31. package/tools/vds-scripts/bitbucket_orchestrator/pyproject.toml +40 -0
  32. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/__init__.py +20 -0
  33. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/async_client.py +657 -0
  34. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/cli.py +2108 -0
  35. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/client.py +2534 -0
  36. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/config.py +171 -0
  37. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/errors.py +67 -0
  38. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/factory.py +185 -0
  39. package/tools/vds-scripts/bitbucket_orchestrator/src/vds_bitbucket_orchestrator/protocols.py +244 -0
  40. package/tools/vds-scripts/bitbucket_orchestrator/tests/__init__.py +8 -0
  41. package/tools/vds-scripts/bitbucket_orchestrator/tests/conftest.py +65 -0
  42. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_advanced_search.py +151 -0
  43. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_async_client.py +546 -0
  44. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_branch_permissions.py +145 -0
  45. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_cli.py +115 -0
  46. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client.py +157 -0
  47. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_branch_conditions.py +79 -0
  48. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_code_advanced.py +163 -0
  49. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_code_file.py +32 -0
  50. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_deployment_environments.py +194 -0
  51. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_issues.py +164 -0
  52. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_pipelines_advanced.py +179 -0
  53. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_pr_blockers.py +119 -0
  54. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_client_repository_variables.py +156 -0
  55. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_code.py +98 -0
  56. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_code_advanced.py +282 -0
  57. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_code_insights.py +335 -0
  58. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_conditions.py +147 -0
  59. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_config.py +131 -0
  60. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_deployment_env.py +352 -0
  61. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_factory.py +371 -0
  62. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_fork_operations.py +204 -0
  63. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_issue_cli.py +261 -0
  64. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_pipeline_advanced.py +270 -0
  65. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_pr_blocker.py +204 -0
  66. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_protocols.py +334 -0
  67. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_repo_settings.py +343 -0
  68. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_repo_variables.py +270 -0
  69. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_webhooks.py +189 -0
  70. package/tools/vds-scripts/bitbucket_orchestrator/tests/test_workspace.py +233 -0
  71. package/tools/vds-scripts/bitbucket_orchestrator/uv.lock +742 -0
  72. package/tools/vds-scripts/confluence_orchestrator/Dockerfile +19 -0
  73. package/tools/vds-scripts/confluence_orchestrator/README.md +412 -0
  74. package/tools/vds-scripts/confluence_orchestrator/SYNC_SCRIPTS.md +127 -0
  75. package/tools/vds-scripts/confluence_orchestrator/SYNC_STANDARDIZATION.md +108 -0
  76. package/tools/vds-scripts/confluence_orchestrator/pyproject.toml +48 -0
  77. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/__init__.py +20 -0
  78. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/cli.py +2532 -0
  79. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/config.py +175 -0
  80. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/content.py +290 -0
  81. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/content_v2.py +94 -0
  82. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/crawl_tree.py +1835 -0
  83. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/errors.py +80 -0
  84. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/eventing.py +109 -0
  85. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/http.py +1114 -0
  86. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/orchestration.py +165 -0
  87. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/reporting.py +78 -0
  88. package/tools/vds-scripts/confluence_orchestrator/src/confluence_orchestrator/tree.py +121 -0
  89. package/tools/vds-scripts/confluence_orchestrator/sync_pdfs_from_markdown.py +213 -0
  90. package/tools/vds-scripts/confluence_orchestrator/sync_pdfs_to_confluence.py +305 -0
  91. package/tools/vds-scripts/confluence_orchestrator/sync_png_attachments.py +305 -0
  92. package/tools/vds-scripts/confluence_orchestrator/tests/__init__.py +0 -0
  93. package/tools/vds-scripts/confluence_orchestrator/tests/conftest.py +8 -0
  94. package/tools/vds-scripts/confluence_orchestrator/tests/test_advanced_content.py +224 -0
  95. package/tools/vds-scripts/confluence_orchestrator/tests/test_advanced_search.py +188 -0
  96. package/tools/vds-scripts/confluence_orchestrator/tests/test_cache_management.py +247 -0
  97. package/tools/vds-scripts/confluence_orchestrator/tests/test_cli.py +499 -0
  98. package/tools/vds-scripts/confluence_orchestrator/tests/test_config.py +83 -0
  99. package/tools/vds-scripts/confluence_orchestrator/tests/test_content.py +186 -0
  100. package/tools/vds-scripts/confluence_orchestrator/tests/test_content_flags.py +27 -0
  101. package/tools/vds-scripts/confluence_orchestrator/tests/test_crawl_tree.py +2250 -0
  102. package/tools/vds-scripts/confluence_orchestrator/tests/test_draft_management.py +223 -0
  103. package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing.py +71 -0
  104. package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing_chaos.py +37 -0
  105. package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing_rate_limit.py +44 -0
  106. package/tools/vds-scripts/confluence_orchestrator/tests/test_eventing_timeout.py +49 -0
  107. package/tools/vds-scripts/confluence_orchestrator/tests/test_export.py +230 -0
  108. package/tools/vds-scripts/confluence_orchestrator/tests/test_history.py +204 -0
  109. package/tools/vds-scripts/confluence_orchestrator/tests/test_http.py +117 -0
  110. package/tools/vds-scripts/confluence_orchestrator/tests/test_orchestration.py +91 -0
  111. package/tools/vds-scripts/confluence_orchestrator/tests/test_reporting.py +24 -0
  112. package/tools/vds-scripts/confluence_orchestrator/tests/test_search_cql.py +34 -0
  113. package/tools/vds-scripts/confluence_orchestrator/tests/test_space_management.py +237 -0
  114. package/tools/vds-scripts/confluence_orchestrator/tests/test_space_permissions.py +332 -0
  115. package/tools/vds-scripts/confluence_orchestrator/tests/test_user_group_management.py +388 -0
  116. package/tools/vds-scripts/confluence_orchestrator/uv.lock +1023 -0
  117. package/tools/vds-scripts/git_orchestrator/ENHANCEMENT_SUMMARY.md +119 -0
  118. package/tools/vds-scripts/git_orchestrator/README.md +280 -0
  119. package/tools/vds-scripts/git_orchestrator/VERIFICATION_REPORT.md +152 -0
  120. package/tools/vds-scripts/git_orchestrator/pyproject.toml +35 -0
  121. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/__init__.py +7 -0
  122. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/__main__.py +4 -0
  123. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/cli.py +847 -0
  124. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/logging_config.py +63 -0
  125. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/manifest.py +129 -0
  126. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/orchestrator.py +819 -0
  127. package/tools/vds-scripts/git_orchestrator/src/vds_git_orchestrator/reporting.py +53 -0
  128. package/tools/vds-scripts/git_orchestrator/tests/__init__.py +0 -0
  129. package/tools/vds-scripts/git_orchestrator/tests/test_cli_settings.py +21 -0
  130. package/tools/vds-scripts/git_orchestrator/tests/test_integration.py +74 -0
  131. package/tools/vds-scripts/git_orchestrator/tests/test_manifest.py +79 -0
  132. package/tools/vds-scripts/git_orchestrator/tests/test_orchestrator.py +204 -0
  133. package/tools/vds-scripts/git_orchestrator/tests/test_public_api.py +236 -0
  134. package/tools/vds-scripts/git_orchestrator/tests/test_resilience.py +345 -0
  135. package/tools/vds-scripts/git_orchestrator/uv.lock +271 -0
  136. package/tools/vds-scripts/jira_orchestrator/README.md +770 -0
  137. package/tools/vds-scripts/jira_orchestrator/pyproject.toml +39 -0
  138. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/__init__.py +1 -0
  139. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/adapter.py +1320 -0
  140. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/cli.py +2271 -0
  141. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/config.py +138 -0
  142. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/errors.py +67 -0
  143. package/tools/vds-scripts/jira_orchestrator/src/vds_jira_orchestrator/reporting.py +65 -0
  144. package/tools/vds-scripts/jira_orchestrator/tests/__init__.py +1 -0
  145. package/tools/vds-scripts/jira_orchestrator/tests/conftest.py +86 -0
  146. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_agile_list_payloads.py +54 -0
  147. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_bulk_operations.py +69 -0
  148. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_components.py +57 -0
  149. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_createmeta.py +45 -0
  150. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_dashboard.py +117 -0
  151. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_issue_properties.py +54 -0
  152. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_permissions_compat.py +42 -0
  153. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_reindex.py +42 -0
  154. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_remote_links.py +76 -0
  155. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_transitions.py +91 -0
  156. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_user_management.py +110 -0
  157. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_version_management.py +133 -0
  158. package/tools/vds-scripts/jira_orchestrator/tests/test_adapter_watchers.py +41 -0
  159. package/tools/vds-scripts/jira_orchestrator/tests/test_advanced_search.py +164 -0
  160. package/tools/vds-scripts/jira_orchestrator/tests/test_agile.py +256 -0
  161. package/tools/vds-scripts/jira_orchestrator/tests/test_application_properties.py +193 -0
  162. package/tools/vds-scripts/jira_orchestrator/tests/test_backlog.py +91 -0
  163. package/tools/vds-scripts/jira_orchestrator/tests/test_bulk_operations.py +277 -0
  164. package/tools/vds-scripts/jira_orchestrator/tests/test_cli.py +106 -0
  165. package/tools/vds-scripts/jira_orchestrator/tests/test_components.py +106 -0
  166. package/tools/vds-scripts/jira_orchestrator/tests/test_config.py +164 -0
  167. package/tools/vds-scripts/jira_orchestrator/tests/test_dashboard.py +122 -0
  168. package/tools/vds-scripts/jira_orchestrator/tests/test_discover_fields.py +207 -0
  169. package/tools/vds-scripts/jira_orchestrator/tests/test_filter_management.py +333 -0
  170. package/tools/vds-scripts/jira_orchestrator/tests/test_issue_archiving.py +164 -0
  171. package/tools/vds-scripts/jira_orchestrator/tests/test_issue_links.py +257 -0
  172. package/tools/vds-scripts/jira_orchestrator/tests/test_issue_properties.py +171 -0
  173. package/tools/vds-scripts/jira_orchestrator/tests/test_link_types.py +314 -0
  174. package/tools/vds-scripts/jira_orchestrator/tests/test_parse_set.py +37 -0
  175. package/tools/vds-scripts/jira_orchestrator/tests/test_permissions.py +273 -0
  176. package/tools/vds-scripts/jira_orchestrator/tests/test_reindex.py +81 -0
  177. package/tools/vds-scripts/jira_orchestrator/tests/test_remote_links.py +254 -0
  178. package/tools/vds-scripts/jira_orchestrator/tests/test_security_schemes.py +170 -0
  179. package/tools/vds-scripts/jira_orchestrator/tests/test_transitions_changelog.py +114 -0
  180. package/tools/vds-scripts/jira_orchestrator/tests/test_user_management.py +226 -0
  181. package/tools/vds-scripts/jira_orchestrator/tests/test_version_management.py +339 -0
  182. package/tools/vds-scripts/jira_orchestrator/tests/test_watchers.py +101 -0
  183. package/tools/vds-scripts/jira_orchestrator/tests/test_worklog.py +223 -0
  184. package/tools/vds-scripts/jira_orchestrator/uv.lock +738 -0
  185. package/tools/vds-scripts/mcp_server/Dockerfile +34 -0
  186. package/tools/vds-scripts/mcp_server/README.md +140 -0
  187. package/tools/vds-scripts/mcp_server/pyproject.toml +42 -0
  188. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/__init__.py +4 -0
  189. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/config.py +36 -0
  190. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/server.py +66 -0
  191. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/__init__.py +14 -0
  192. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/bitbucket_tools.py +47 -0
  193. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/confluence_tools.py +59 -0
  194. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/git_tools.py +71 -0
  195. package/tools/vds-scripts/mcp_server/src/vds_mcp_server/tools/jira_tools.py +63 -0
  196. package/tools/vds-scripts/mcp_server/tests/__init__.py +2 -0
  197. package/tools/vds-scripts/mcp_server/tests/conftest.py +29 -0
  198. package/tools/vds-scripts/mcp_server/tests/unit/__init__.py +2 -0
  199. package/tools/vds-scripts/mcp_server/tests/unit/test_bitbucket_tools.py +25 -0
  200. package/tools/vds-scripts/mcp_server/tests/unit/test_confluence_tools.py +25 -0
  201. package/tools/vds-scripts/mcp_server/tests/unit/test_git_tools.py +32 -0
  202. package/tools/vds-scripts/mcp_server/tests/unit/test_jira_tools.py +32 -0
  203. package/tools/vds-scripts/mcp_server/tests/verification/__init__.py +2 -0
  204. package/tools/vds-scripts/mcp_server/tests/verification/test_mcp_confluence_tools.py +40 -0
  205. package/tools/vds-scripts/mcp_server/tests/verification/test_mcp_jira_tools.py +37 -0
  206. package/tools/vds-scripts/mcp_server/tests/verification/test_mcp_tool_registration.py +47 -0
  207. package/tools/vds-scripts/mcp_server/uv.lock +1032 -0
  208. package/tools/vds-scripts/mypy.ini +5 -0
  209. package/tools/vds-scripts/pyproject.toml +29 -0
  210. package/tools/vds-scripts/repo-manifest.yaml +273 -0
  211. package/tools/vds-scripts/repo-manifest.yaml.example +25 -0
  212. package/tools/vds-scripts/scripts/BRD-Validation-API.postman_collection.json +706 -0
  213. package/tools/vds-scripts/scripts/BRD-Validation-README.md +308 -0
  214. package/tools/vds-scripts/scripts/README.md +162 -0
  215. package/tools/vds-scripts/scripts/bootstrap_uv.sh +30 -0
  216. package/tools/vds-scripts/scripts/brd-validation-environment.json +51 -0
  217. package/tools/vds-scripts/scripts/brd-validation-test-results.json +13023 -0
  218. package/tools/vds-scripts/scripts/brd_coverage_report.json +276 -0
  219. package/tools/vds-scripts/scripts/create_memory_session.py +35 -0
  220. package/tools/vds-scripts/scripts/deployment/load_docker_images_offline.sh +90 -0
  221. package/tools/vds-scripts/scripts/final_completion_report.md +139 -0
  222. package/tools/vds-scripts/scripts/folder_structure_report.json +321 -0
  223. package/tools/vds-scripts/scripts/generate_completion_report.py +125 -0
  224. package/tools/vds-scripts/scripts/generate_intellij_modules.py +150 -0
  225. package/tools/vds-scripts/scripts/link_integrity_report.json +807 -0
  226. package/tools/vds-scripts/scripts/move_audit_artifact_pages.py +255 -0
  227. package/tools/vds-scripts/scripts/move_audit_artifact_pages_rest.py +165 -0
  228. package/tools/vds-scripts/scripts/move_wrong_dept_pages.py +216 -0
  229. package/tools/vds-scripts/scripts/save_intellij_memories.py +120 -0
  230. package/tools/vds-scripts/scripts/save_memories_to_vds_ai.py +83 -0
  231. package/tools/vds-scripts/scripts/save_memories_vds_style.py +129 -0
  232. package/tools/vds-scripts/scripts/search_intellij_memories.py +50 -0
  233. package/tools/vds-scripts/scripts/setup_intellij_workspace.py +65 -0
  234. package/tools/vds-scripts/scripts/target-state-automation/README.md +89 -0
  235. package/tools/vds-scripts/scripts/target-state-automation/confluence_sync_coordinator.sh +27 -0
  236. package/tools/vds-scripts/scripts/target-state-automation/coordination.sh +114 -0
  237. package/tools/vds-scripts/scripts/target-state-automation/diagram_coordinator.sh +25 -0
  238. package/tools/vds-scripts/scripts/target-state-automation/docs_root.sh +22 -0
  239. package/tools/vds-scripts/scripts/target-state-automation/generate_diagrams.sh +22 -0
  240. package/tools/vds-scripts/scripts/target-state-automation/markdown_coordinator.sh +25 -0
  241. package/tools/vds-scripts/scripts/target-state-automation/progress_dashboard.sh +17 -0
  242. package/tools/vds-scripts/scripts/target-state-automation/schema_coordinator.sh +25 -0
  243. package/tools/vds-scripts/scripts/target-state-automation/sync_confluence.sh +30 -0
  244. package/tools/vds-scripts/scripts/target-state-automation/update_dependencies.sh +19 -0
  245. package/tools/vds-scripts/scripts/target-state-automation/validate_links.sh +86 -0
  246. package/tools/vds-scripts/scripts/target-state-automation/validate_markdown.sh +52 -0
  247. package/tools/vds-scripts/scripts/target-state-automation/validate_schemas.sh +26 -0
  248. package/tools/vds-scripts/scripts/target-state-automation/validate_structure.sh +98 -0
  249. package/tools/vds-scripts/scripts/update_modules_xml.py +190 -0
  250. package/tools/vds-scripts/scripts/uv-workspace-alignment-verification-2026-03-25.md +128 -0
  251. package/tools/vds-scripts/scripts/validate_brd_coverage.py +179 -0
  252. package/tools/vds-scripts/scripts/validate_folder_structure.py +240 -0
  253. package/tools/vds-scripts/scripts/validate_link_integrity.py +272 -0
  254. package/tools/vds-scripts/scripts/vds_sh_helpers.sh +180 -0
  255. package/tools/vds-scripts/scripts/verification/phase2_portable_paths_ubuntu_docker.sh +26 -0
  256. package/tools/vds-scripts/scripts/worktree_uv.sh +48 -0
  257. package/tools/vds-scripts/uv.lock +8 -0
  258. package/tools/vds-scripts/vds_cli/README.md +126 -0
  259. package/tools/vds-scripts/vds_cli/VERIFICATION_REPORT.md +41 -0
  260. package/tools/vds-scripts/vds_cli/pyproject.toml +38 -0
  261. package/tools/vds-scripts/vds_cli/src/vds_cli/__init__.py +3 -0
  262. package/tools/vds-scripts/vds_cli/src/vds_cli/cli.py +173 -0
  263. package/tools/vds-scripts/vds_cli/src/vds_cli/docs_sync.py +1203 -0
  264. package/tools/vds-scripts/vds_cli/src/vds_cli/env.py +41 -0
  265. package/tools/vds-scripts/vds_cli/src/vds_cli/google_sheets_orchestrator/__init__.py +3 -0
  266. package/tools/vds-scripts/vds_cli/src/vds_cli/google_sheets_orchestrator/google_sheets_orchestrator.py +198 -0
  267. package/tools/vds-scripts/vds_cli/src/vds_cli/router.py +93 -0
  268. package/tools/vds-scripts/vds_cli/src/vds_cli/sync_api.py +647 -0
  269. package/tools/vds-scripts/vds_cli/src/vds_cli/sync_service.py +266 -0
  270. package/tools/vds-scripts/vds_cli/tests/__init__.py +2 -0
  271. package/tools/vds-scripts/vds_cli/tests/conftest.py +49 -0
  272. package/tools/vds-scripts/vds_cli/tests/unit/__init__.py +2 -0
  273. package/tools/vds-scripts/vds_cli/tests/unit/test_cli.py +143 -0
  274. package/tools/vds-scripts/vds_cli/tests/unit/test_docs_sync.py +422 -0
  275. package/tools/vds-scripts/vds_cli/tests/unit/test_env.py +51 -0
  276. package/tools/vds-scripts/vds_cli/tests/unit/test_router.py +72 -0
  277. package/tools/vds-scripts/vds_cli/tests/unit/test_sync_api.py +357 -0
  278. package/tools/vds-scripts/vds_cli/tests/unit/test_sync_service.py +160 -0
  279. package/tools/vds-scripts/vds_cli/tests/verification/__init__.py +2 -0
  280. package/tools/vds-scripts/vds_cli/tests/verification/test_bitbucket_real.py +33 -0
  281. package/tools/vds-scripts/vds_cli/tests/verification/test_confluence_real.py +35 -0
  282. package/tools/vds-scripts/vds_cli/tests/verification/test_jira_real.py +41 -0
  283. package/tools/vds-scripts/vds_cli/uv.lock +524 -0
  284. package/tools/vds-scripts/vds_cli_common/README.md +190 -0
  285. package/tools/vds-scripts/vds_cli_common/pyproject.toml +92 -0
  286. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/__init__.py +34 -0
  287. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/completers.py +139 -0
  288. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/context.py +201 -0
  289. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/env.py +119 -0
  290. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/errors.py +318 -0
  291. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/output.py +284 -0
  292. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/paths.py +78 -0
  293. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/testing.py +213 -0
  294. package/tools/vds-scripts/vds_cli_common/src/vds_cli_common/version.py +85 -0
  295. package/tools/vds-scripts/vds_cli_common/tests/__init__.py +1 -0
  296. package/tools/vds-scripts/vds_cli_common/tests/test_completers.py +148 -0
  297. package/tools/vds-scripts/vds_cli_common/tests/test_context.py +192 -0
  298. package/tools/vds-scripts/vds_cli_common/tests/test_env.py +102 -0
  299. package/tools/vds-scripts/vds_cli_common/tests/test_errors.py +186 -0
  300. package/tools/vds-scripts/vds_cli_common/tests/test_output.py +229 -0
  301. package/tools/vds-scripts/vds_cli_common/tests/test_paths.py +61 -0
  302. package/tools/vds-scripts/vds_cli_common/tests/test_testing.py +138 -0
  303. package/tools/vds-scripts/vds_cli_common/tests/test_version.py +64 -0
@@ -0,0 +1,175 @@
1
+ """Configuration loading utilities for the Confluence orchestrator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import Iterable, Mapping
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+
10
+ from pydantic import AnyHttpUrl, BaseModel, Field, ValidationError
11
+
12
+ _PROJECT_ROOT = Path(__file__).resolve().parents[3]
13
+ _SCRIPTS_DIR = _PROJECT_ROOT.parent
14
+
15
+ DEFAULT_ENV_PATHS = (
16
+ Path.home() / ".vds" / ".env",
17
+ _SCRIPTS_DIR / ".env",
18
+ )
19
+
20
+ _REQUIRED_KEYS: tuple[str, ...] = (
21
+ "VDS_USERNAME",
22
+ "VDS_PASSWORD",
23
+ "INTERNAL_CONFLUENCE_TOKEN",
24
+ "EXTERNAL_CONFLUENCE_TOKEN",
25
+ )
26
+
27
+ _OPTIONAL_KEYS: tuple[str, ...] = (
28
+ "CONFLUENCE_DEFAULT_SERVER",
29
+ "CONFLUENCE_INTERNAL_URL",
30
+ "CONFLUENCE_EXTERNAL_URL",
31
+ "CONFLUENCE_MAX_RETRIES",
32
+ "CONFLUENCE_RETRY_BACKOFF",
33
+ )
34
+
35
+
36
+ class SettingsError(RuntimeError):
37
+ """Raised when the environment configuration cannot be loaded."""
38
+
39
+
40
+ class ConfluenceSettings(BaseModel):
41
+ """Strongly-typed view of the orchestrator settings."""
42
+
43
+ username: str | None = Field(default=None, alias="VDS_USERNAME")
44
+ password: str | None = Field(default=None, alias="VDS_PASSWORD")
45
+
46
+ internal_token: str | None = Field(default=None, alias="INTERNAL_CONFLUENCE_TOKEN")
47
+ external_token: str | None = Field(default=None, alias="EXTERNAL_CONFLUENCE_TOKEN")
48
+
49
+ internal_url: AnyHttpUrl = Field(
50
+ default="http://confluence.digital.vn",
51
+ alias="CONFLUENCE_INTERNAL_URL",
52
+ )
53
+ external_url: AnyHttpUrl = Field(
54
+ default="http://10.254.136.35:8090",
55
+ alias="CONFLUENCE_EXTERNAL_URL",
56
+ )
57
+
58
+ default_server: str = Field(default="internal", alias="CONFLUENCE_DEFAULT_SERVER")
59
+
60
+ # Retry configuration
61
+ max_retries: int = Field(default=3, alias="CONFLUENCE_MAX_RETRIES")
62
+ retry_backoff_factor: float = Field(default=1.0, alias="CONFLUENCE_RETRY_BACKOFF")
63
+
64
+ class Config:
65
+ populate_by_name = True
66
+
67
+ def token_for(self, server: str) -> str | None:
68
+ server_lower = server.lower()
69
+ if server_lower in {"internal", "int", "main"}:
70
+ return self.internal_token
71
+ if server_lower in {"external", "ext", "outsource"}:
72
+ return self.external_token
73
+ raise ValueError(f"Unknown server '{server}'")
74
+
75
+ def url_for(self, server: str) -> AnyHttpUrl:
76
+ server_lower = server.lower()
77
+ if server_lower in {"internal", "int", "main"}:
78
+ return self.internal_url
79
+ if server_lower in {"external", "ext", "outsource"}:
80
+ return self.external_url
81
+ raise ValueError(f"Unknown server '{server}'")
82
+
83
+ @property
84
+ def default_token(self) -> str | None:
85
+ return self.token_for(self.default_server)
86
+
87
+ @property
88
+ def default_url(self) -> AnyHttpUrl:
89
+ return self.url_for(self.default_server)
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class EnvSnapshot:
94
+ """A snapshot of relevant environment variables."""
95
+
96
+ values: Mapping[str, str]
97
+ source: Path | str | None
98
+
99
+
100
+ def _parse_env_file(env_path: Path) -> dict[str, str]:
101
+ if not env_path.exists():
102
+ return {}
103
+
104
+ result: dict[str, str] = {}
105
+ for raw_line in env_path.read_text().splitlines():
106
+ line = raw_line.strip()
107
+ if not line or line.startswith("#"):
108
+ continue
109
+ if "=" not in line:
110
+ continue
111
+ key, raw_value = line.split("=", 1)
112
+ key = key.strip()
113
+ value = raw_value.strip().strip('"').strip("'")
114
+ if key:
115
+ result[key] = value
116
+ return result
117
+
118
+
119
+ def _merge_env_sources(*snapshots: EnvSnapshot) -> dict[str, str]:
120
+ merged: dict[str, str] = {}
121
+ for snapshot in snapshots:
122
+ merged.update(snapshot.values)
123
+ return merged
124
+
125
+
126
+ def _filter_keys(source: Mapping[str, str]) -> dict[str, str]:
127
+ relevant_keys = set(_REQUIRED_KEYS + _OPTIONAL_KEYS)
128
+ return {key: value for key, value in source.items() if key in relevant_keys}
129
+
130
+
131
+ def _resolve_env_paths(env_path: Path | None) -> Iterable[Path]:
132
+ if env_path is not None:
133
+ return (env_path,)
134
+ return DEFAULT_ENV_PATHS
135
+
136
+
137
+ def load_settings(
138
+ *,
139
+ env_path: Path | None = None,
140
+ overrides: Mapping[str, str] | None = None,
141
+ strict: bool = False,
142
+ ) -> ConfluenceSettings:
143
+ """Load orchestrator settings from the environment and optional .env file."""
144
+
145
+ snapshots = []
146
+ for candidate in _resolve_env_paths(env_path):
147
+ snapshots.append(
148
+ EnvSnapshot(
149
+ values=_filter_keys(_parse_env_file(candidate)), source=candidate if candidate.exists() else None
150
+ )
151
+ )
152
+
153
+ snapshots.append(EnvSnapshot(values=_filter_keys(os.environ), source="os.environ"))
154
+
155
+ if overrides:
156
+ snapshots.append(EnvSnapshot(values=_filter_keys(overrides), source="overrides"))
157
+
158
+ merged = _merge_env_sources(*snapshots)
159
+
160
+ try:
161
+ settings = ConfluenceSettings.model_validate(merged)
162
+ except ValidationError as exc:
163
+ raise SettingsError(str(exc)) from exc
164
+
165
+ if strict:
166
+ has_token = bool(settings.internal_token or settings.external_token)
167
+ if not has_token:
168
+ raise SettingsError(
169
+ "Missing Confluence bearer tokens. Run 'uv run --project vds_cli vds-cli env load' before invoking the CLI."
170
+ )
171
+
172
+ return settings
173
+
174
+
175
+ __all__ = ["ConfluenceSettings", "SettingsError", "load_settings", "DEFAULT_ENV_PATHS"]
@@ -0,0 +1,290 @@
1
+ """Confluence REST v1 helpers covering read and write flows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from contextlib import ExitStack
7
+ from pathlib import Path
8
+ from typing import Any, BinaryIO
9
+
10
+ from .http import ConfluenceClient
11
+
12
+ FileLike = str | Path | BinaryIO
13
+
14
+
15
+ class ContentClient:
16
+ """Operations against Confluence content endpoints (REST v1)."""
17
+
18
+ def __init__(self, http_client: ConfluenceClient) -> None:
19
+ self._http = http_client
20
+
21
+ # ------------------------------------------------------------------
22
+ # Read-only endpoints
23
+ # ------------------------------------------------------------------
24
+
25
+ def get_page(
26
+ self,
27
+ page_id: str,
28
+ *,
29
+ expand: Iterable[str] | None = None,
30
+ ) -> dict[str, Any]:
31
+ return self._http.get_page(page_id, expand=list(expand) if expand else None)
32
+
33
+ def search_cql(
34
+ self,
35
+ cql: str,
36
+ *,
37
+ limit: int = 25,
38
+ start: int = 0,
39
+ expand: Iterable[str] | None = None,
40
+ ) -> dict[str, Any]:
41
+ return self._http.search_cql(cql, limit=limit, expand=list(expand) if expand else None, start=start)
42
+
43
+ # ------------------------------------------------------------------
44
+ # Mutating endpoints
45
+ # ------------------------------------------------------------------
46
+
47
+ def create_page(
48
+ self,
49
+ *,
50
+ space_key: str,
51
+ title: str,
52
+ body: str,
53
+ parent_id: str | None = None,
54
+ representation: str = "storage",
55
+ status: str = "current",
56
+ notify_watchers: bool | None = None,
57
+ ) -> dict[str, Any]:
58
+ if notify_watchers:
59
+ raise ValueError("notify_watchers is not supported by the Confluence Server SDK; omit the flag.")
60
+
61
+ return self._http.create_page(
62
+ space_key,
63
+ title,
64
+ body,
65
+ parent_id=parent_id,
66
+ representation=representation,
67
+ status=status,
68
+ )
69
+
70
+ def update_page(
71
+ self,
72
+ page_id: str,
73
+ *,
74
+ title: str | None = None,
75
+ body: str | None = None,
76
+ space_key: str | None = None,
77
+ representation: str = "storage",
78
+ version: int | None = None,
79
+ minor_edit: bool = False,
80
+ message: str | None = None,
81
+ status: str = "current",
82
+ notify_watchers: bool | None = None,
83
+ ) -> dict[str, Any]:
84
+ if title is None and body is None:
85
+ raise ValueError("At least one of title/body must be provided when updating a page")
86
+
87
+ # Get current page info if needed
88
+ if title is None:
89
+ current = self.get_page(page_id, expand=["version", "space", f"body.{representation}"])
90
+ title = current["title"]
91
+
92
+ if notify_watchers:
93
+ raise ValueError("notify_watchers is not supported by the Confluence Server SDK; omit the flag.")
94
+
95
+ return self._http.update_page(
96
+ page_id,
97
+ title,
98
+ body,
99
+ version=version,
100
+ representation=representation,
101
+ minor_edit=minor_edit,
102
+ version_comment=message,
103
+ status=status,
104
+ )
105
+
106
+ def delete_page(self, page_id: str, *, status: str = "current") -> None:
107
+ self._http.delete_page(page_id)
108
+
109
+ def upload_attachment(
110
+ self,
111
+ page_id: str,
112
+ file: FileLike,
113
+ *,
114
+ filename: str | None = None,
115
+ content_type: str | None = None,
116
+ comment: str | None = None,
117
+ ) -> dict[str, Any]:
118
+ with ExitStack() as stack:
119
+ stream, resolved_name = self._prepare_file(file, filename, stack)
120
+ # Read file content into memory for atlassian library
121
+ content = stream.read()
122
+
123
+ return self._http.add_attachment(
124
+ page_id,
125
+ resolved_name,
126
+ content,
127
+ content_type=content_type,
128
+ comment=comment,
129
+ )
130
+
131
+ def list_attachments(self, page_id: str, *, expand: Iterable[str] | None = None) -> dict[str, Any]:
132
+ """List attachments for a specific page."""
133
+ result = self._http.get_attachments(page_id)
134
+ if isinstance(result, dict):
135
+ return result
136
+ return {"results": result or []}
137
+
138
+ def download_attachment(
139
+ self,
140
+ attachment_id: str,
141
+ output_path: str | Path,
142
+ *,
143
+ filename: str | None = None,
144
+ ) -> Path:
145
+ """Download an attachment by ID to a local file."""
146
+ content = self._http.get_attachment_content(attachment_id)
147
+ output = Path(output_path)
148
+ if output.is_dir():
149
+ filename = filename or f"attachment_{attachment_id}"
150
+ output = output / filename
151
+ elif filename:
152
+ output = output.parent / filename
153
+ else:
154
+ output.parent.mkdir(parents=True, exist_ok=True)
155
+
156
+ output.write_bytes(content)
157
+ return output
158
+
159
+ def update_attachment(
160
+ self,
161
+ page_id: str,
162
+ attachment_id: str,
163
+ file: FileLike,
164
+ *,
165
+ filename: str | None = None,
166
+ content_type: str | None = None,
167
+ comment: str | None = None,
168
+ ) -> dict[str, Any]:
169
+ """Update an existing attachment."""
170
+ with ExitStack() as stack:
171
+ stream, resolved_name = self._prepare_file(file, filename, stack)
172
+ content = stream.read()
173
+
174
+ return self._http.update_attachment_data(
175
+ page_id,
176
+ attachment_id,
177
+ resolved_name,
178
+ content,
179
+ content_type=content_type,
180
+ comment=comment,
181
+ )
182
+
183
+ def delete_attachment(
184
+ self,
185
+ page_id: str,
186
+ attachment_id: str | None = None,
187
+ *,
188
+ version: int | None = None,
189
+ filename: str | None = None,
190
+ ) -> None:
191
+ """Delete an attachment by ID or filename.
192
+
193
+ If ``filename`` is provided we delegate to the SDK's delete-by-filename helper,
194
+ which deletes the current attachment (all versions when ``version`` is None).
195
+ Otherwise we fall back to deleting by attachment ID/version.
196
+ """
197
+
198
+ if filename:
199
+ self._http.delete_attachment(page_id, filename, version=version)
200
+ return
201
+
202
+ if not attachment_id:
203
+ raise ValueError("Provide attachment ID or --filename for deletion")
204
+
205
+ self._http.delete_attachment_by_id(page_id, attachment_id, version=version)
206
+
207
+ # ------------------------------------------------------------------
208
+ # Template operations
209
+ # ------------------------------------------------------------------
210
+
211
+ def list_templates(self, space_key: str | None = None) -> dict[str, Any]:
212
+ params: dict[str, Any] = {}
213
+ if space_key:
214
+ params["spaceKey"] = space_key
215
+ response = self._http.request("GET", "/rest/api/template", params=params)
216
+ payload = response.json()
217
+ return payload if isinstance(payload, dict) else {"results": payload}
218
+
219
+ def create_template(
220
+ self,
221
+ *,
222
+ name: str,
223
+ body: str,
224
+ template_type: str = "page",
225
+ space_key: str | None = None,
226
+ description: str | None = None,
227
+ representation: str = "storage",
228
+ ) -> dict[str, Any]:
229
+ payload: dict[str, Any] = {
230
+ "name": name,
231
+ "body": {"value": body, "representation": representation},
232
+ "templateType": template_type,
233
+ }
234
+ if space_key:
235
+ payload["space"] = {"key": space_key}
236
+ if description:
237
+ payload["description"] = description
238
+ response = self._http.request("POST", "/rest/api/template", json=payload)
239
+ return response.json()
240
+
241
+ def get_template(self, template_id: str) -> dict[str, Any]:
242
+ response = self._http.request("GET", f"/rest/api/template/{template_id}")
243
+ return response.json()
244
+
245
+ def update_template(
246
+ self,
247
+ template_id: str,
248
+ *,
249
+ name: str | None = None,
250
+ body: str | None = None,
251
+ description: str | None = None,
252
+ representation: str = "storage",
253
+ template_type: str | None = None,
254
+ ) -> dict[str, Any]:
255
+ existing = self.get_template(template_id)
256
+ payload = {
257
+ "name": name or existing.get("name"),
258
+ "templateType": template_type or existing.get("templateType", "page"),
259
+ "description": description or existing.get("description"),
260
+ "body": body and {"value": body, "representation": representation} or existing.get("body"),
261
+ }
262
+ response = self._http.request("PUT", f"/rest/api/template/{template_id}", json=payload)
263
+ return response.json()
264
+
265
+ def delete_template(self, template_id: str) -> None:
266
+ self._http.request("DELETE", f"/rest/api/template/{template_id}")
267
+
268
+ # ------------------------------------------------------------------
269
+ # Helpers
270
+ # ------------------------------------------------------------------
271
+
272
+ def _prepare_file(
273
+ self,
274
+ file: FileLike,
275
+ filename: str | None,
276
+ stack: ExitStack,
277
+ ) -> tuple[BinaryIO, str]:
278
+ if isinstance(file, (str, Path)):
279
+ path = Path(file)
280
+ stream = stack.enter_context(path.open("rb"))
281
+ name = filename or path.name
282
+ return stream, name
283
+
284
+ stream = file # BinaryIO
285
+ inferred_name = getattr(stream, "name", None)
286
+ resolved = filename or (Path(inferred_name).name if inferred_name else "attachment")
287
+ return stream, resolved
288
+
289
+
290
+ __all__ = ["ContentClient"]
@@ -0,0 +1,94 @@
1
+ """Confluence Cloud REST v2 client utilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from typing import Any
7
+
8
+ from .http import ConfluenceClient
9
+
10
+
11
+ class ContentClientV2:
12
+ """Minimal adapter for Confluence Cloud REST v2 endpoints."""
13
+
14
+ def __init__(self, http_client: ConfluenceClient) -> None:
15
+ self._http = http_client
16
+
17
+ def get_page(
18
+ self,
19
+ page_id: str,
20
+ *,
21
+ expand: Iterable[str] | None = None,
22
+ ) -> dict[str, Any]:
23
+ return self._http.get_page(page_id, expand=list(expand) if expand else None)
24
+
25
+ def search_cql(
26
+ self,
27
+ cql: str,
28
+ *,
29
+ limit: int = 25,
30
+ cursor: str | None = None,
31
+ expand: Iterable[str] | None = None,
32
+ ) -> dict[str, Any]:
33
+ return self._http.search_cql(cql, limit=limit, expand=list(expand) if expand else None)
34
+
35
+ def list_templates(
36
+ self,
37
+ space_key: str | None = None,
38
+ limit: int = 25,
39
+ cursor: str | None = None,
40
+ ) -> dict[str, Any]:
41
+ params = {"limit": limit}
42
+ if space_key:
43
+ params["spaceKey"] = space_key
44
+ if cursor:
45
+ params["cursor"] = cursor
46
+ response = self._http.request("GET", "/wiki/api/v2/content-templates", params=params)
47
+ return response.json()
48
+
49
+ def get_content(
50
+ self,
51
+ page_id: str,
52
+ *,
53
+ expand: Iterable[str] | None = None,
54
+ format: str | None = None,
55
+ ) -> dict[str, Any]:
56
+ """Get content by page ID - alias for get_page for compatibility."""
57
+ return self.get_page(page_id, expand=expand)
58
+
59
+ def list_attachments(self, page_id: str, *, expand: Iterable[str] | None = None) -> dict[str, Any]:
60
+ """List attachments for a specific page."""
61
+ params: dict[str, Any] = {}
62
+ if expand:
63
+ params["expand"] = ",".join(expand)
64
+ response = self._http.request("GET", f"/wiki/api/v2/pages/{page_id}/attachments", params=params)
65
+ return response.json()
66
+
67
+ def upload_attachment(
68
+ self, page_id: str, file, filename: str | None = None, comment: str | None = None
69
+ ) -> dict[str, Any]:
70
+ """Upload attachment to a page using Confluence v2 API."""
71
+ from pathlib import Path
72
+
73
+ if isinstance(file, (str, Path)):
74
+ file_path = Path(file)
75
+ if not file_path.exists():
76
+ raise FileNotFoundError(f"File not found: {file_path}")
77
+ filename = filename or file_path.name
78
+ with open(file_path, "rb") as f:
79
+ files = {"file": (filename, f, "application/octet-stream")}
80
+ data = {"comment": comment} if comment else None
81
+ response = self._http.request(
82
+ "POST", f"/wiki/api/v2/pages/{page_id}/attachments", files=files, data=data
83
+ )
84
+ return response.json()
85
+ else:
86
+ # Handle file-like objects
87
+ filename = filename or "upload"
88
+ files = {"file": (filename, file, "application/octet-stream")}
89
+ data = {"comment": comment} if comment else None
90
+ response = self._http.request("POST", f"/wiki/api/v2/pages/{page_id}/attachments", files=files, data=data)
91
+ return response.json()
92
+
93
+
94
+ __all__ = ["ContentClientV2"]