botrun-horse 2.29.2 → 2.30.0

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 (574) hide show
  1. package/botrun-c/.claude/skills/DOCX/351/226/261/350/256/200/346/212/200/350/203/275/SKILL.md +103 -0
  2. package/botrun-c/.claude/skills/DOCX/351/226/261/350/256/200/346/212/200/350/203/275/scripts/docx-to-markdown.mjs +206 -0
  3. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/SKILL.md +1093 -0
  4. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-branch.mjs +73 -0
  5. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-branches.mjs +77 -0
  6. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-checkout.mjs +72 -0
  7. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-clone.mjs +72 -0
  8. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-commit.mjs +75 -0
  9. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-delete-branch.mjs +75 -0
  10. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-delete-file.mjs +72 -0
  11. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-diff.mjs +80 -0
  12. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-list-tree.mjs +336 -0
  13. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-list.mjs +199 -0
  14. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-merge.mjs +86 -0
  15. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-move.mjs +75 -0
  16. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-pr-create.mjs +81 -0
  17. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-pr-list.mjs +74 -0
  18. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-pr-merge.mjs +83 -0
  19. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-pr-view.mjs +71 -0
  20. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-push.mjs +71 -0
  21. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-read.mjs +277 -0
  22. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-search.mjs +370 -0
  23. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-stash.mjs +116 -0
  24. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-sync.mjs +71 -0
  25. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/github-write.mjs +74 -0
  26. package/botrun-c/.claude/skills/GitHub/346/212/200/350/203/275/scripts/lib/path-validator.mjs +167 -0
  27. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/SKILL.md +605 -0
  28. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-create.mjs +127 -0
  29. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-delete.mjs +77 -0
  30. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-diff.mjs +87 -0
  31. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-history.mjs +99 -0
  32. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-list.mjs +174 -0
  33. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-read.mjs +214 -0
  34. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-restore.mjs +75 -0
  35. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-search.mjs +270 -0
  36. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-sync-back.mjs +155 -0
  37. package/botrun-c/.claude/skills/GoogleDrive/346/212/200/350/203/275/scripts/gdrive-update.mjs +100 -0
  38. package/botrun-c/.claude/skills/HTML/347/224/237/346/210/220/346/212/200/350/203/275/SKILL.md +414 -0
  39. package/botrun-c/.claude/skills/HTML/347/224/237/346/210/220/346/212/200/350/203/275/scripts/create-project.mjs +91 -0
  40. package/botrun-c/.claude/skills/HTML/347/224/237/346/210/220/346/212/200/350/203/275/scripts/finalize-project.mjs +162 -0
  41. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/SKILL.md +206 -0
  42. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/scripts/package.json +19 -0
  43. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/scripts/pdf-analyze.mjs +309 -0
  44. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/scripts/pdf-compress.mjs +315 -0
  45. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/scripts/pdf-split.mjs +275 -0
  46. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/scripts/pdf-to-text.mjs +336 -0
  47. package/botrun-c/.claude/skills/PDF/346/212/200/350/203/275/scripts/test-pdf-scripts.mjs +491 -0
  48. package/botrun-c/.claude/skills/docx-to-markdown/SKILL.md +76 -0
  49. package/botrun-c/.claude/skills/docx-to-markdown/scripts/convert_docx.py +183 -0
  50. package/botrun-c/.claude/skills/gcp-coord-ml-ocr/SKILL.md +133 -0
  51. package/botrun-c/.claude/skills/gcp-coord-ml-ocr/scripts/ocr_processor.py +381 -0
  52. package/botrun-c/.claude/skills/gemini-transcribe/SKILL.md +115 -0
  53. package/botrun-c/.claude/skills/gemini-transcribe/scripts/transcribe.py +499 -0
  54. package/botrun-c/.claude/skills/nchc-transcribe/SKILL.md +131 -0
  55. package/botrun-c/.claude/skills/nchc-transcribe/scripts/transcribe.py +522 -0
  56. package/botrun-c/.claude/skills/p320-moj-review/SKILL.md +200 -0
  57. package/botrun-c/.claude/skills/p320-moj-review/references/guideline.md +118 -0
  58. package/botrun-c/.claude/skills/pdf-multimodal-processor/SKILL.md +186 -0
  59. package/botrun-c/.claude/skills/pdf-multimodal-processor/scripts/process_pdf.py +515 -0
  60. package/botrun-c/.claude/skills/ripgrep/346/220/234/345/260/213/346/212/200/350/203/275/SKILL.md +81 -0
  61. package/botrun-c/.claude/skills/ripgrep/346/220/234/345/260/213/346/212/200/350/203/275/scripts/package.json +13 -0
  62. package/botrun-c/.claude/skills/ripgrep/346/220/234/345/260/213/346/212/200/350/203/275/scripts/secure-search.mjs +232 -0
  63. package/botrun-c/.claude/skills/top100/351/240/230/350/242/226/351/240/220/346/270/254/346/212/200/350/203/275/SKILL.md +518 -0
  64. package/botrun-c/.claude/skills//345/216/273/350/255/230/345/210/245/346/212/200/350/203/275/SKILL.md +125 -0
  65. package/botrun-c/.claude/skills//345/233/236/346/206/266/346/212/200/350/203/275/SKILL.md +123 -0
  66. package/botrun-c/.claude/skills//345/233/236/346/206/266/346/212/200/350/203/275/scripts/recall.mjs +339 -0
  67. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/SKILL.md +156 -0
  68. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/__tests__/utils.test.mjs +139 -0
  69. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/constants.mjs +40 -0
  70. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/gcs-uploader.mjs +195 -0
  71. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/gemini-image-client.mjs +307 -0
  72. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/generate-image.mjs +103 -0
  73. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/image-session-manager.mjs +219 -0
  74. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/output-formatter.mjs +209 -0
  75. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/package.json +20 -0
  76. package/botrun-c/.claude/skills//345/234/226/347/211/207/347/224/237/346/210/220/346/212/200/350/203/275/scripts/utils.mjs +115 -0
  77. package/botrun-c/.claude/skills//345/244/232/346/250/241/346/205/213/351/226/261/350/256/200/346/212/200/350/203/275/SKILL.md +86 -0
  78. package/botrun-c/.claude/skills//345/244/232/346/250/241/346/205/213/351/226/261/350/256/200/346/212/200/350/203/275/scripts/multimodal-read.mjs +304 -0
  79. package/botrun-c/.claude/skills//345/244/232/346/250/241/346/205/213/351/226/261/350/256/200/346/212/200/350/203/275/scripts/package.json +17 -0
  80. package/botrun-c/.claude/skills//345/255/265/345/214/226/346/212/200/350/203/275/SKILL.md +131 -0
  81. package/botrun-c/.claude/skills//345/255/265/345/214/226/346/212/200/350/203/275/scripts/skill-manager.mjs +542 -0
  82. package/botrun-c/.claude/skills//346/234/203/350/255/260/350/250/230/351/214/204/346/212/200/350/203/275/SKILL.md +127 -0
  83. package/botrun-c/.claude/skills//347/233/256/351/214/204/345/210/227/350/241/250/346/212/200/350/203/275/SKILL.md +53 -0
  84. package/botrun-c/.claude/skills//347/233/256/351/214/204/345/210/227/350/241/250/346/212/200/350/203/275/scripts/package.json +13 -0
  85. package/botrun-c/.claude/skills//347/233/256/351/214/204/345/210/227/350/241/250/346/212/200/350/203/275/scripts/tree-view.mjs +149 -0
  86. package/botrun-c/.claude/skills//347/264/224/346/226/207/345/255/227/345/244/232/346/252/224/346/241/210/350/256/200/345/217/226/346/212/200/350/203/275/SKILL.md +82 -0
  87. package/botrun-c/.claude/skills//347/264/224/346/226/207/345/255/227/345/244/232/346/252/224/346/241/210/350/256/200/345/217/226/346/212/200/350/203/275/scripts/package.json +16 -0
  88. package/botrun-c/.claude/skills//347/264/224/346/226/207/345/255/227/345/244/232/346/252/224/346/241/210/350/256/200/345/217/226/346/212/200/350/203/275/scripts/secure-read.mjs +260 -0
  89. package/botrun-c/.claude/skills//347/266/262/350/267/257/346/220/234/345/260/213/346/212/200/350/203/275/SKILL.md +156 -0
  90. package/botrun-c/.claude/skills//347/266/262/350/267/257/346/220/234/345/260/213/346/212/200/350/203/275/scripts/package.json +16 -0
  91. package/botrun-c/.claude/skills//347/266/262/350/267/257/346/220/234/345/260/213/346/212/200/350/203/275/scripts/search.js +139 -0
  92. package/botrun-c/.claude/skills//347/266/262/350/267/257/346/220/234/345/260/213/346/212/200/350/203/275/scripts/search.mjs +272 -0
  93. package/botrun-c/.claude/skills//347/266/262/350/267/257/346/220/234/345/260/213/346/212/200/350/203/275/scripts/search.ts +166 -0
  94. package/botrun-c/.claude/skills//350/250/230/346/206/266/346/212/200/350/203/275/SKILL.md +114 -0
  95. package/botrun-c/.claude/skills//350/250/230/346/206/266/346/212/200/350/203/275/scripts/remember.mjs +159 -0
  96. package/botrun-c/.claude/skills//350/250/230/346/206/266/346/212/200/350/203/275/scripts/test-advanced.mjs +342 -0
  97. package/botrun-c/.claude/skills//350/250/230/346/206/266/346/212/200/350/203/275/scripts/test.mjs +430 -0
  98. package/botrun-c/.claude/skills//350/252/236/351/237/263/350/275/211/346/226/207/345/255/227/346/212/200/350/203/275/SKILL.md +30 -0
  99. package/botrun-c/.claude/skills//350/252/236/351/237/263/350/275/211/346/226/207/345/255/227/346/212/200/350/203/275/scripts/package.json +13 -0
  100. package/botrun-c/.claude/skills//350/252/236/351/237/263/350/275/211/346/226/207/345/255/227/346/212/200/350/203/275/scripts/transcribe.mjs +294 -0
  101. package/botrun-c/.claude/skills//351/233/266/345/271/273/350/246/272/350/255/211/346/230/216/346/212/200/350/203/275/SKILL.md +338 -0
  102. package/botrun-c/.dockerignore +131 -0
  103. package/botrun-c/.dockeroptimize +13 -0
  104. package/botrun-c/.firebase/hosting.b3V0.cache +30 -0
  105. package/botrun-c/.firebaserc +5 -0
  106. package/botrun-c/.gcloudignore +56 -0
  107. package/botrun-c/.ruby-version +1 -0
  108. package/botrun-c/CHANGELOG.md +224 -0
  109. package/botrun-c/Dockerfile.gcp +365 -0
  110. package/botrun-c/Dockerfile.slim +195 -0
  111. package/botrun-c/Gemfile +8 -0
  112. package/botrun-c/Gemfile.lock +233 -0
  113. package/botrun-c/LOCAL-DEVELOPMENT-GUIDE.md +393 -0
  114. package/botrun-c/PLAN.md +131 -0
  115. package/botrun-c/README.md +129 -0
  116. package/botrun-c/TODO-CAPACITOR-INTEGRATION.md +482 -0
  117. package/botrun-c/VERIFICATION.md +121 -0
  118. package/botrun-c/admin/RATE_LIMIT_MANAGEMENT.md +312 -0
  119. package/botrun-c/admin/README.md +338 -0
  120. package/botrun-c/admin/botrun-billing-report-1765235244038.csv +22 -0
  121. package/botrun-c/admin/botrun-billing-report-1765235323089.csv +22 -0
  122. package/botrun-c/admin/botrun-dashboard-1765235244039.html +278 -0
  123. package/botrun-c/admin/botrun-dashboard-1765235323089.html +278 -0
  124. package/botrun-c/admin/botrun-timeseries-074xGzIKWyfKTBt1NMIj9lxi5mO2-1765235244039.html +161 -0
  125. package/botrun-c/admin/botrun-timeseries-074xGzIKWyfKTBt1NMIj9lxi5mO2-1765235323090.html +161 -0
  126. package/botrun-c/admin/check-billing.ts +48 -0
  127. package/botrun-c/admin/count-all-users.ts +65 -0
  128. package/botrun-c/admin/generate-analytics-report.ts +188 -0
  129. package/botrun-c/admin/jest.config.ts +43 -0
  130. package/botrun-c/admin/lib/__tests__/formatters.test.ts +264 -0
  131. package/botrun-c/admin/lib/__tests__/mocks/firebase.mock.ts +112 -0
  132. package/botrun-c/admin/lib/__tests__/services/UserService.test.ts +83 -0
  133. package/botrun-c/admin/lib/__tests__/user-resolver.test.ts +439 -0
  134. package/botrun-c/admin/lib/analyzers/BillingAnalyticsService.ts +242 -0
  135. package/botrun-c/admin/lib/analyzers/CsvExporter.ts +263 -0
  136. package/botrun-c/admin/lib/analyzers/HtmlChartGenerator.ts +530 -0
  137. package/botrun-c/admin/lib/analyzers/SpikeAnalyzer.ts +608 -0
  138. package/botrun-c/admin/lib/analyzers/TimeSeriesAnalyzer.ts +344 -0
  139. package/botrun-c/admin/lib/cli/CLIBootstrapper.ts +40 -0
  140. package/botrun-c/admin/lib/commands/AbstractCommand.ts +69 -0
  141. package/botrun-c/admin/lib/commands/AnalyticsCommand.ts +215 -0
  142. package/botrun-c/admin/lib/commands/ListCommand.ts +85 -0
  143. package/botrun-c/admin/lib/commands/QueryCommand.ts +81 -0
  144. package/botrun-c/admin/lib/commands/SpikeCommand.ts +83 -0
  145. package/botrun-c/admin/lib/domain/User.ts +38 -0
  146. package/botrun-c/admin/lib/firebase.ts +92 -0
  147. package/botrun-c/admin/lib/firestore-safe.ts +105 -0
  148. package/botrun-c/admin/lib/formatters.ts +154 -0
  149. package/botrun-c/admin/lib/rate-limit-detailed.ts +355 -0
  150. package/botrun-c/admin/lib/rate-limit.ts +286 -0
  151. package/botrun-c/admin/lib/repositories/UserRepository.ts +104 -0
  152. package/botrun-c/admin/lib/services/UserService.ts +108 -0
  153. package/botrun-c/admin/lib/types.ts +56 -0
  154. package/botrun-c/admin/lib/user-resolver.ts +275 -0
  155. package/botrun-c/admin/manage-auth-domains.js +178 -0
  156. package/botrun-c/admin/migrate-plan-free.sh +64 -0
  157. package/botrun-c/admin/package-lock.json +5656 -0
  158. package/botrun-c/admin/package.json +28 -0
  159. package/botrun-c/admin/rate-limit.sh +55 -0
  160. package/botrun-c/admin/remove-custom-limits.ts +75 -0
  161. package/botrun-c/admin/reset-rate.sh +70 -0
  162. package/botrun-c/admin/reset-trial.sh +68 -0
  163. package/botrun-c/admin/seed-trial-data.sh +38 -0
  164. package/botrun-c/admin/trial.sh +55 -0
  165. package/botrun-c/admin/tsconfig.json +20 -0
  166. package/botrun-c/admin/users-cli.py +298 -0
  167. package/botrun-c/admin/users.sh +12 -0
  168. package/botrun-c/admin/users.ts +1321 -0
  169. package/botrun-c/api/admin-tools/check-whitelist.mjs.migration-bak +74 -0
  170. package/botrun-c/api/admin-tools/create-user.mjs +92 -0
  171. package/botrun-c/api/admin-tools/create-user.mjs.migration-bak +92 -0
  172. package/botrun-c/api/admin-tools/firebase-auth-domains-v2.py +287 -0
  173. package/botrun-c/api/admin-tools/firebase-auth-domains-v2.py.migration-bak +287 -0
  174. package/botrun-c/api/admin-tools/init-firestore.mjs +75 -0
  175. package/botrun-c/api/admin-tools/init-firestore.mjs.migration-bak +101 -0
  176. package/botrun-c/api/admin-tools/list-users.mjs +43 -0
  177. package/botrun-c/api/admin-tools/list-users.mjs.migration-bak +43 -0
  178. package/botrun-c/api/admin-tools/manage-authorized-domains.ts +255 -0
  179. package/botrun-c/api/admin-tools/manage-authorized-domains.ts.migration-bak +255 -0
  180. package/botrun-c/api/admin-tools/setup-custom-claims.ts.migration-bak +155 -0
  181. package/botrun-c/api/admin-tools/test-claims.mjs +69 -0
  182. package/botrun-c/api/admin-tools/test-claims.mjs.migration-bak +69 -0
  183. package/botrun-c/api/admin-tools/update-service-urls-gcloud.mjs +72 -0
  184. package/botrun-c/api/admin-tools/update-service-urls-gcloud.mjs.migration-bak +72 -0
  185. package/botrun-c/api/admin-tools/update-service-urls-to-lb.mjs +159 -0
  186. package/botrun-c/api/admin-tools/update-service-urls-to-lb.mjs.migration-bak +159 -0
  187. package/botrun-c/api/admin-tools/update-service-urls.mjs +82 -0
  188. package/botrun-c/api/admin-tools/update-service-urls.mjs.migration-bak +82 -0
  189. package/botrun-c/api/package-lock.json +5031 -0
  190. package/botrun-c/api/package.json +63 -0
  191. package/botrun-c/api/pnpm-lock.yaml +4157 -0
  192. package/botrun-c/api/rate-limit-export-1763620678737.csv +79 -0
  193. package/botrun-c/api/scripts/README-migrate-plan-free.md +197 -0
  194. package/botrun-c/api/scripts/check-users.ts +45 -0
  195. package/botrun-c/api/scripts/delete-firestore-docs.js +43 -0
  196. package/botrun-c/api/scripts/dump-subscriptions.ts +395 -0
  197. package/botrun-c/api/scripts/list-all-users.ts +65 -0
  198. package/botrun-c/api/scripts/migrate-plan-free-rpd-16to32.ts +262 -0
  199. package/botrun-c/api/scripts/migrate-plan-names-to-lite-max.ts +269 -0
  200. package/botrun-c/api/scripts/query-user-info.ts +304 -0
  201. package/botrun-c/api/scripts/reset-rate-limit.ts +206 -0
  202. package/botrun-c/api/scripts/reset-trial-subscription.ts +166 -0
  203. package/botrun-c/api/scripts/rollback-plan-names-from-lite-max.ts +139 -0
  204. package/botrun-c/api/scripts/seed-rate-limit-test-data.ts +114 -0
  205. package/botrun-c/api/scripts/seed-subscription-test-data.ts +173 -0
  206. package/botrun-c/api/scripts/test-rate-limit-message.ts +108 -0
  207. package/botrun-c/api/test-billing-write.ts +103 -0
  208. package/botrun-c/api/test-billing.ts +81 -0
  209. package/botrun-c/api/test-chat-upload.sh +162 -0
  210. package/botrun-c/api/test-nchc-whisper.ts +87 -0
  211. package/botrun-c/api/test-skills-loading.js +77 -0
  212. package/botrun-c/api/test-skills-symlink.js +118 -0
  213. package/botrun-c/api/test-upload-multimodal.sh +167 -0
  214. package/botrun-c/api/tsconfig.json +27 -0
  215. package/botrun-c/api/verify-form-2-1.mjs +229 -0
  216. package/botrun-c/api/vitest.config.ts +9 -0
  217. package/botrun-c/appium-capabilities.json +20 -0
  218. package/botrun-c/cors.json +26 -0
  219. package/botrun-c/delete-old.sh +44 -0
  220. package/botrun-c/design/google-drive/01-architecture.md +186 -0
  221. package/botrun-c/design/google-drive/02-api-design.md +314 -0
  222. package/botrun-c/design/google-drive/03-frontend-design.md +278 -0
  223. package/botrun-c/design/google-drive/04-skill-design.md +384 -0
  224. package/botrun-c/design/google-drive/05-test-plan.md +507 -0
  225. package/botrun-c/design/google-drive/README.md +32 -0
  226. package/botrun-c/dev/mcp-token-calculator/README.md +92 -0
  227. package/botrun-c/dev/mcp-token-calculator/RESULTS.md +188 -0
  228. package/botrun-c/dev/mcp-token-calculator/calculate-current-tokens.ts +369 -0
  229. package/botrun-c/dev/mcp-token-calculator/calculate-mcp-tokens.ts +464 -0
  230. package/botrun-c/dev/mcp-token-calculator/optimization-proposals.ts +316 -0
  231. package/botrun-c/dev/mcp-token-calculator/package-lock.json +599 -0
  232. package/botrun-c/dev/mcp-token-calculator/package.json +22 -0
  233. package/botrun-c/dev/mcp-token-calculator/test-all-schemas.ts +314 -0
  234. package/botrun-c/dev/mcp-token-calculator/test-schema-correctness.ts +221 -0
  235. package/botrun-c/dev/mcp-token-calculator/verify-optimization.ts +159 -0
  236. package/botrun-c/fastlane/ANDROID-DEPLOYMENT-SETUP.md +825 -0
  237. package/botrun-c/fastlane/IOS-DEPLOYMENT-SETUP.md +431 -0
  238. package/botrun-c/fastlane/Pluginfile +5 -0
  239. package/botrun-c/fastlane/QUICK-START.md +133 -0
  240. package/botrun-c/fastlane/README.md +424 -0
  241. package/botrun-c/fastlane/RUBY-SETUP.md +449 -0
  242. package/botrun-c/fastlane/android/Appfile +11 -0
  243. package/botrun-c/fastlane/android/Fastfile +240 -0
  244. package/botrun-c/firebase.json +46 -0
  245. package/botrun-c/firestore-init-data.json +40 -0
  246. package/botrun-c/firestore.indexes.json +55 -0
  247. package/botrun-c/firestore.rules +53 -0
  248. package/botrun-c/fix-codex-permissions.sh +56 -0
  249. package/botrun-c/gcs-lifecycle-30day-retention.json +12 -0
  250. package/botrun-c/html-architecture/architecture/deployment.html +664 -0
  251. package/botrun-c/html-architecture/architecture/index.html +309 -0
  252. package/botrun-c/html-architecture/architecture/styles.css +500 -0
  253. package/botrun-c/html-architecture/architecture/system-components.html +538 -0
  254. package/botrun-c/html-architecture/architecture/user-flow.html +370 -0
  255. package/botrun-c/mobile-android/FIREBASE-AUTH-SETUP.md +302 -0
  256. package/botrun-c/mobile-android/WSL-ANDROID-SETUP.md +252 -0
  257. package/botrun-c/mobile-android/android/app/build.gradle +75 -0
  258. package/botrun-c/mobile-android/android/app/capacitor.build.gradle +31 -0
  259. package/botrun-c/mobile-android/android/app/proguard-rules.pro +21 -0
  260. package/botrun-c/mobile-android/android/build.gradle +29 -0
  261. package/botrun-c/mobile-android/android/capacitor.settings.gradle +42 -0
  262. package/botrun-c/mobile-android/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  263. package/botrun-c/mobile-android/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  264. package/botrun-c/mobile-android/android/gradle.properties +22 -0
  265. package/botrun-c/mobile-android/android/gradlew +252 -0
  266. package/botrun-c/mobile-android/android/gradlew.bat +94 -0
  267. package/botrun-c/mobile-android/android/settings.gradle +5 -0
  268. package/botrun-c/mobile-android/android/variables.gradle +16 -0
  269. package/botrun-c/mobile-android/capacitor.config.ts +44 -0
  270. package/botrun-c/mobile-android/fastlane/Appfile +11 -0
  271. package/botrun-c/mobile-android/fastlane/Fastfile +202 -0
  272. package/botrun-c/mobile-android/fastlane/README.md +96 -0
  273. package/botrun-c/mobile-android/google-services.json.template +39 -0
  274. package/botrun-c/mobile-android/package.json +32 -0
  275. package/botrun-c/mobile-android/resources/icon.png +0 -0
  276. package/botrun-c/mobile-android/tsconfig.json +15 -0
  277. package/botrun-c/mobile-ios/.ruby-version +1 -0
  278. package/botrun-c/mobile-ios/BUILD-LOG.md +82 -0
  279. package/botrun-c/mobile-ios/Gemfile +4 -0
  280. package/botrun-c/mobile-ios/Gemfile.lock +299 -0
  281. package/botrun-c/mobile-ios/GoogleService-Info.plist.template +34 -0
  282. package/botrun-c/mobile-ios/IOS-PERMISSIONS.md +80 -0
  283. package/botrun-c/mobile-ios/QUICK-START.md +189 -0
  284. package/botrun-c/mobile-ios/README.md +231 -0
  285. package/botrun-c/mobile-ios/capacitor.config.ts +46 -0
  286. package/botrun-c/mobile-ios/fastlane/Fastfile +326 -0
  287. package/botrun-c/mobile-ios/fastlane/README.md +88 -0
  288. package/botrun-c/mobile-ios/ios/App/App/App.entitlements +10 -0
  289. package/botrun-c/mobile-ios/ios/App/App/AppDelegate.swift +49 -0
  290. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png +0 -0
  291. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json +14 -0
  292. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Contents.json +6 -0
  293. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json +56 -0
  294. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany-dark.png +0 -0
  295. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany.png +0 -0
  296. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany-dark.png +0 -0
  297. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany.png +0 -0
  298. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany-dark.png +0 -0
  299. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany.png +0 -0
  300. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png +0 -0
  301. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png +0 -0
  302. package/botrun-c/mobile-ios/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png +0 -0
  303. package/botrun-c/mobile-ios/ios/App/App/Base.lproj/LaunchScreen.storyboard +32 -0
  304. package/botrun-c/mobile-ios/ios/App/App/Base.lproj/Main.storyboard +19 -0
  305. package/botrun-c/mobile-ios/ios/App/App/Info.plist +72 -0
  306. package/botrun-c/mobile-ios/ios/App/App.xcodeproj/project.pbxproj +446 -0
  307. package/botrun-c/mobile-ios/ios/App/App.xcodeproj/xcshareddata/xcschemes/App.xcscheme +78 -0
  308. package/botrun-c/mobile-ios/ios/App/App.xcworkspace/contents.xcworkspacedata +10 -0
  309. package/botrun-c/mobile-ios/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  310. package/botrun-c/mobile-ios/ios/App/Podfile +52 -0
  311. package/botrun-c/mobile-ios/ios/App/Podfile.lock +205 -0
  312. package/botrun-c/mobile-ios/ios/App/add_plist.rb +30 -0
  313. package/botrun-c/mobile-ios/ios/App/fix_plist_path.rb +22 -0
  314. package/botrun-c/mobile-ios/package.json +29 -0
  315. package/botrun-c/mobile-ios/resources/icon.png +0 -0
  316. package/botrun-c/mobile-ios/run-simulator.sh +34 -0
  317. package/botrun-c/mobile-ios/screenshot-after-clean-rebuild.png +0 -0
  318. package/botrun-c/mobile-ios/screenshot-final-version.png +0 -0
  319. package/botrun-c/mobile-ios/screenshot-simulator.png +0 -0
  320. package/botrun-c/mobile-ios/screenshot-version-1009.png +0 -0
  321. package/botrun-c/mobile-ios/screenshot-with-version.png +0 -0
  322. package/botrun-c/mobile-ios/screenshot-working.png +0 -0
  323. package/botrun-c/mobile-ios/setup-ios.sh +106 -0
  324. package/botrun-c/mobile-ios/tsconfig.json +16 -0
  325. package/botrun-c/org-policy-allow-all-users.yaml +3 -0
  326. package/botrun-c/package.json +68 -0
  327. package/botrun-c/pnpm-lock.yaml +10198 -0
  328. package/botrun-c/pnpm-workspace.yaml +6 -0
  329. package/botrun-c/prompts/BOTRUN-1.md +13 -0
  330. package/botrun-c/prompts/BOTRUN-2.md +70 -0
  331. package/botrun-c/prompts/BOTRUN-3.md +139 -0
  332. package/botrun-c/prototypes/firestore-gax-debug/package-lock.json +1204 -0
  333. package/botrun-c/prototypes/firestore-gax-debug/package.json +12 -0
  334. package/botrun-c/prototypes/firestore-gax-debug/pnpm-workspace.yaml +2 -0
  335. package/botrun-c/prototypes/firestore-gax-debug/test.js +98 -0
  336. package/botrun-c/prototypes/taiwan-haiku/README.md +93 -0
  337. package/botrun-c/prototypes/taiwan-haiku/index.ts +139 -0
  338. package/botrun-c/prototypes/taiwan-haiku/package.json +23 -0
  339. package/botrun-c/prototypes/taiwan-haiku/pnpm-lock.yaml +730 -0
  340. package/botrun-c/prototypes/taiwan-haiku/test-vertex-direct.ts +83 -0
  341. package/botrun-c/prototypes/taiwan-haiku/tsconfig.json +18 -0
  342. package/botrun-c/prototypes/web-search/FINDINGS-STAGE-1.md +48 -0
  343. package/botrun-c/prototypes/web-search/README.md +46 -0
  344. package/botrun-c/prototypes/web-search/VERIFICATION-REPORT.md +310 -0
  345. package/botrun-c/prototypes/web-search/package-lock.json +595 -0
  346. package/botrun-c/prototypes/web-search/package.json +18 -0
  347. package/botrun-c/prototypes/web-search/stage-1.1-basic-connection.js +123 -0
  348. package/botrun-c/prototypes/web-search/stage-1.2-with-websearch.js +154 -0
  349. package/botrun-c/prototypes/web-search/stage-1.3-search-query.js +192 -0
  350. package/botrun-c/prototypes/web-search/stage-2-agent-sdk-websearch.js +265 -0
  351. package/botrun-c/runtime-scripts/env-detect.sh +64 -0
  352. package/botrun-c/scripts/github-collaborator-manager.sh +297 -0
  353. package/botrun-c/scripts/github-collaborator-manager.ts +325 -0
  354. package/botrun-c/scripts/manage-collaborators.sh +91 -0
  355. package/botrun-c/scripts/setup-secrets.sh +202 -0
  356. package/botrun-c/scripts/stress-test-17-users.sh +113 -0
  357. package/botrun-c/scripts/stress-test-api.sh +119 -0
  358. package/botrun-c/scripts/stress-test-full-report.sh +385 -0
  359. package/botrun-c/specs/infra-vm/design.md +0 -0
  360. package/botrun-c/specs/share-skill/design.md +1146 -0
  361. package/botrun-c/specs/share-skill/tasks.md +444 -0
  362. package/botrun-c/stress-test-results/20260121-162630/config.json +7 -0
  363. package/botrun-c/stress-test-results/20260121-162630/user-1-status.txt +1 -0
  364. package/botrun-c/stress-test-results/20260121-162630/user-1-timing.txt +1 -0
  365. package/botrun-c/stress-test-results/20260121-162630/user-1.json +2 -0
  366. package/botrun-c/stress-test-results/20260121-162646/config.json +7 -0
  367. package/botrun-c/stress-test-results/20260121-162646/user-1-status.txt +1 -0
  368. package/botrun-c/stress-test-results/20260121-162646/user-1-timing.txt +1 -0
  369. package/botrun-c/stress-test-results/20260121-162646/user-1.json +2 -0
  370. package/botrun-c/stress-test-results/20260121-162646/user-10-status.txt +1 -0
  371. package/botrun-c/stress-test-results/20260121-162646/user-10-timing.txt +1 -0
  372. package/botrun-c/stress-test-results/20260121-162646/user-10.json +2 -0
  373. package/botrun-c/stress-test-results/20260121-162646/user-11-status.txt +1 -0
  374. package/botrun-c/stress-test-results/20260121-162646/user-11-timing.txt +1 -0
  375. package/botrun-c/stress-test-results/20260121-162646/user-11.json +2 -0
  376. package/botrun-c/stress-test-results/20260121-162646/user-12-status.txt +1 -0
  377. package/botrun-c/stress-test-results/20260121-162646/user-12-timing.txt +1 -0
  378. package/botrun-c/stress-test-results/20260121-162646/user-12.json +2 -0
  379. package/botrun-c/stress-test-results/20260121-162646/user-13-status.txt +1 -0
  380. package/botrun-c/stress-test-results/20260121-162646/user-13-timing.txt +1 -0
  381. package/botrun-c/stress-test-results/20260121-162646/user-13.json +2 -0
  382. package/botrun-c/stress-test-results/20260121-162646/user-14-status.txt +1 -0
  383. package/botrun-c/stress-test-results/20260121-162646/user-14-timing.txt +1 -0
  384. package/botrun-c/stress-test-results/20260121-162646/user-14.json +2 -0
  385. package/botrun-c/stress-test-results/20260121-162646/user-15-status.txt +1 -0
  386. package/botrun-c/stress-test-results/20260121-162646/user-15-timing.txt +1 -0
  387. package/botrun-c/stress-test-results/20260121-162646/user-15.json +2 -0
  388. package/botrun-c/stress-test-results/20260121-162646/user-16-status.txt +1 -0
  389. package/botrun-c/stress-test-results/20260121-162646/user-16-timing.txt +1 -0
  390. package/botrun-c/stress-test-results/20260121-162646/user-16.json +2 -0
  391. package/botrun-c/stress-test-results/20260121-162646/user-17-status.txt +1 -0
  392. package/botrun-c/stress-test-results/20260121-162646/user-17-timing.txt +1 -0
  393. package/botrun-c/stress-test-results/20260121-162646/user-17.json +2 -0
  394. package/botrun-c/stress-test-results/20260121-162646/user-2-status.txt +1 -0
  395. package/botrun-c/stress-test-results/20260121-162646/user-2-timing.txt +1 -0
  396. package/botrun-c/stress-test-results/20260121-162646/user-2.json +2 -0
  397. package/botrun-c/stress-test-results/20260121-162646/user-3-status.txt +1 -0
  398. package/botrun-c/stress-test-results/20260121-162646/user-3-timing.txt +1 -0
  399. package/botrun-c/stress-test-results/20260121-162646/user-3.json +2 -0
  400. package/botrun-c/stress-test-results/20260121-162646/user-4-status.txt +1 -0
  401. package/botrun-c/stress-test-results/20260121-162646/user-4-timing.txt +1 -0
  402. package/botrun-c/stress-test-results/20260121-162646/user-4.json +2 -0
  403. package/botrun-c/stress-test-results/20260121-162646/user-5-status.txt +1 -0
  404. package/botrun-c/stress-test-results/20260121-162646/user-5-timing.txt +1 -0
  405. package/botrun-c/stress-test-results/20260121-162646/user-5.json +2 -0
  406. package/botrun-c/stress-test-results/20260121-162646/user-6-status.txt +1 -0
  407. package/botrun-c/stress-test-results/20260121-162646/user-6-timing.txt +1 -0
  408. package/botrun-c/stress-test-results/20260121-162646/user-6.json +2 -0
  409. package/botrun-c/stress-test-results/20260121-162646/user-7-status.txt +1 -0
  410. package/botrun-c/stress-test-results/20260121-162646/user-7-timing.txt +1 -0
  411. package/botrun-c/stress-test-results/20260121-162646/user-7.json +2 -0
  412. package/botrun-c/stress-test-results/20260121-162646/user-8-status.txt +1 -0
  413. package/botrun-c/stress-test-results/20260121-162646/user-8-timing.txt +1 -0
  414. package/botrun-c/stress-test-results/20260121-162646/user-8.json +2 -0
  415. package/botrun-c/stress-test-results/20260121-162646/user-9-status.txt +1 -0
  416. package/botrun-c/stress-test-results/20260121-162646/user-9-timing.txt +1 -0
  417. package/botrun-c/stress-test-results/20260121-162646/user-9.json +2 -0
  418. package/botrun-c/stress-test-results/20260121-163148/REPORT.html +344 -0
  419. package/botrun-c/stress-test-results/20260121-163148/REPORT.md +93 -0
  420. package/botrun-c/stress-test-results/20260121-163148/REPORT.pdf +0 -0
  421. package/botrun-c/stress-test-results/20260121-163148/config.json +7 -0
  422. package/botrun-c/stress-test-results/20260121-163148/summary.json +26 -0
  423. package/botrun-c/stress-test-results/20260121-163148/user-1-status.txt +1 -0
  424. package/botrun-c/stress-test-results/20260121-163148/user-1-timing.txt +1 -0
  425. package/botrun-c/stress-test-results/20260121-163148/user-1.json +206 -0
  426. package/botrun-c/stress-test-results/20260121-163148/user-10-status.txt +1 -0
  427. package/botrun-c/stress-test-results/20260121-163148/user-10-timing.txt +1 -0
  428. package/botrun-c/stress-test-results/20260121-163148/user-10.json +110 -0
  429. package/botrun-c/stress-test-results/20260121-163148/user-11-status.txt +1 -0
  430. package/botrun-c/stress-test-results/20260121-163148/user-11-timing.txt +1 -0
  431. package/botrun-c/stress-test-results/20260121-163148/user-11.json +104 -0
  432. package/botrun-c/stress-test-results/20260121-163148/user-12-status.txt +1 -0
  433. package/botrun-c/stress-test-results/20260121-163148/user-12-timing.txt +1 -0
  434. package/botrun-c/stress-test-results/20260121-163148/user-12.json +98 -0
  435. package/botrun-c/stress-test-results/20260121-163148/user-13-status.txt +1 -0
  436. package/botrun-c/stress-test-results/20260121-163148/user-13-timing.txt +1 -0
  437. package/botrun-c/stress-test-results/20260121-163148/user-13.json +68 -0
  438. package/botrun-c/stress-test-results/20260121-163148/user-14-status.txt +1 -0
  439. package/botrun-c/stress-test-results/20260121-163148/user-14-timing.txt +1 -0
  440. package/botrun-c/stress-test-results/20260121-163148/user-14.json +116 -0
  441. package/botrun-c/stress-test-results/20260121-163148/user-15-status.txt +1 -0
  442. package/botrun-c/stress-test-results/20260121-163148/user-15-timing.txt +1 -0
  443. package/botrun-c/stress-test-results/20260121-163148/user-15.json +131 -0
  444. package/botrun-c/stress-test-results/20260121-163148/user-16-status.txt +1 -0
  445. package/botrun-c/stress-test-results/20260121-163148/user-16-timing.txt +1 -0
  446. package/botrun-c/stress-test-results/20260121-163148/user-16.json +110 -0
  447. package/botrun-c/stress-test-results/20260121-163148/user-17-status.txt +1 -0
  448. package/botrun-c/stress-test-results/20260121-163148/user-17-timing.txt +1 -0
  449. package/botrun-c/stress-test-results/20260121-163148/user-17.json +116 -0
  450. package/botrun-c/stress-test-results/20260121-163148/user-2-status.txt +1 -0
  451. package/botrun-c/stress-test-results/20260121-163148/user-2-timing.txt +1 -0
  452. package/botrun-c/stress-test-results/20260121-163148/user-2.json +98 -0
  453. package/botrun-c/stress-test-results/20260121-163148/user-3-status.txt +1 -0
  454. package/botrun-c/stress-test-results/20260121-163148/user-3-timing.txt +1 -0
  455. package/botrun-c/stress-test-results/20260121-163148/user-3.json +62 -0
  456. package/botrun-c/stress-test-results/20260121-163148/user-4-status.txt +1 -0
  457. package/botrun-c/stress-test-results/20260121-163148/user-4-timing.txt +1 -0
  458. package/botrun-c/stress-test-results/20260121-163148/user-4.json +113 -0
  459. package/botrun-c/stress-test-results/20260121-163148/user-5-status.txt +1 -0
  460. package/botrun-c/stress-test-results/20260121-163148/user-5-timing.txt +1 -0
  461. package/botrun-c/stress-test-results/20260121-163148/user-5.json +83 -0
  462. package/botrun-c/stress-test-results/20260121-163148/user-6-status.txt +1 -0
  463. package/botrun-c/stress-test-results/20260121-163148/user-6-timing.txt +1 -0
  464. package/botrun-c/stress-test-results/20260121-163148/user-6.json +125 -0
  465. package/botrun-c/stress-test-results/20260121-163148/user-7-status.txt +1 -0
  466. package/botrun-c/stress-test-results/20260121-163148/user-7-timing.txt +1 -0
  467. package/botrun-c/stress-test-results/20260121-163148/user-7.json +62 -0
  468. package/botrun-c/stress-test-results/20260121-163148/user-8-status.txt +1 -0
  469. package/botrun-c/stress-test-results/20260121-163148/user-8-timing.txt +1 -0
  470. package/botrun-c/stress-test-results/20260121-163148/user-8.json +41 -0
  471. package/botrun-c/stress-test-results/20260121-163148/user-9-status.txt +1 -0
  472. package/botrun-c/stress-test-results/20260121-163148/user-9-timing.txt +1 -0
  473. package/botrun-c/stress-test-results/20260121-163148/user-9.json +53 -0
  474. package/botrun-c/test-merge.md +1 -0
  475. package/botrun-c/version.txt +1 -0
  476. package/botrun-c/web/.version +1 -0
  477. package/botrun-c/web/ALLOW-PUBLIC-ACCESS.md +57 -0
  478. package/botrun-c/web/FINAL-SETUP-STEPS.md +71 -0
  479. package/botrun-c/web/FIREBASE-MANUAL-STEPS.md +41 -0
  480. package/botrun-c/web/QUICK-SETUP-HELPER.md +79 -0
  481. package/botrun-c/web/README.md +162 -0
  482. package/botrun-c/web/__tests__/image-extractor.test.ts +147 -0
  483. package/botrun-c/web/__tests__/useGitHub.test.ts +245 -0
  484. package/botrun-c/web/app/favicon.ico +0 -0
  485. package/botrun-c/web/app/globals.css +215 -0
  486. package/botrun-c/web/app/image-preview/page.tsx +213 -0
  487. package/botrun-c/web/app/layout.tsx +46 -0
  488. package/botrun-c/web/app/page.tsx +45 -0
  489. package/botrun-c/web/components/CapacitorProvider.tsx +43 -0
  490. package/botrun-c/web/components/ChatPage.tsx +1736 -0
  491. package/botrun-c/web/components/ConversationList.tsx +237 -0
  492. package/botrun-c/web/components/ConversationListItem.tsx +121 -0
  493. package/botrun-c/web/components/LoginPage.tsx +212 -0
  494. package/botrun-c/web/components/Providers.tsx +12 -0
  495. package/botrun-c/web/components/StatusBarProvider.tsx +16 -0
  496. package/botrun-c/web/components/attachment-preview.tsx +193 -0
  497. package/botrun-c/web/components/botrun-incubator/BotrunIncubator.tsx +266 -0
  498. package/botrun-c/web/components/botrun-incubator/index.ts +1 -0
  499. package/botrun-c/web/components/chat-input.tsx +1251 -0
  500. package/botrun-c/web/components/chat-message.tsx +598 -0
  501. package/botrun-c/web/components/drop-zone.tsx +143 -0
  502. package/botrun-c/web/components/gdrive/gdrive-add-dialog.tsx +254 -0
  503. package/botrun-c/web/components/gdrive/gdrive-drawer.tsx +236 -0
  504. package/botrun-c/web/components/gdrive/gdrive-selector.tsx +226 -0
  505. package/botrun-c/web/components/gdrive/index.ts +7 -0
  506. package/botrun-c/web/components/generated-image-card.tsx +363 -0
  507. package/botrun-c/web/components/github/GitHubConnect.tsx +366 -0
  508. package/botrun-c/web/components/github/index.ts +2 -0
  509. package/botrun-c/web/components/import-skill-dialog.tsx +208 -0
  510. package/botrun-c/web/components/scroll-to-bottom-button.tsx +90 -0
  511. package/botrun-c/web/components/share-skill-dialog.tsx +175 -0
  512. package/botrun-c/web/components/skills-editor.tsx +443 -0
  513. package/botrun-c/web/components/skills-manager-drawer.tsx +586 -0
  514. package/botrun-c/web/components/smart-button-group.tsx +132 -0
  515. package/botrun-c/web/config/smart-buttons.ts +295 -0
  516. package/botrun-c/web/config/welcome-messages.ts +29 -0
  517. package/botrun-c/web/contexts/AuthContext.tsx +220 -0
  518. package/botrun-c/web/hooks/use-toast.ts +187 -0
  519. package/botrun-c/web/hooks/useAppStoreUpdate.ts +356 -0
  520. package/botrun-c/web/hooks/useAppVersion.ts +113 -0
  521. package/botrun-c/web/hooks/useAttachments.ts +269 -0
  522. package/botrun-c/web/hooks/useAuthToken.ts +59 -0
  523. package/botrun-c/web/hooks/useAutoWelcome.ts +89 -0
  524. package/botrun-c/web/hooks/useCapacitorPlatform.ts +113 -0
  525. package/botrun-c/web/hooks/useCapawesomeLiveUpdate.ts +149 -0
  526. package/botrun-c/web/hooks/useClaudeModel.ts +118 -0
  527. package/botrun-c/web/hooks/useConversation.ts +115 -0
  528. package/botrun-c/web/hooks/useConversations.ts +235 -0
  529. package/botrun-c/web/hooks/useGdriveFolders.ts +199 -0
  530. package/botrun-c/web/hooks/useGdriveSync.ts +107 -0
  531. package/botrun-c/web/hooks/useGitHub.ts +409 -0
  532. package/botrun-c/web/hooks/useScrollToBottom.ts +150 -0
  533. package/botrun-c/web/hooks/useStatusBar.ts +42 -0
  534. package/botrun-c/web/hooks/useUserPlan.ts +153 -0
  535. package/botrun-c/web/lib/api-config.ts +162 -0
  536. package/botrun-c/web/lib/api-url.ts +62 -0
  537. package/botrun-c/web/lib/api.ts +105 -0
  538. package/botrun-c/web/lib/attachment-utils.ts +168 -0
  539. package/botrun-c/web/lib/build-version.ts +11 -0
  540. package/botrun-c/web/lib/clipboard-utils.ts +199 -0
  541. package/botrun-c/web/lib/constants.ts +62 -0
  542. package/botrun-c/web/lib/conversation-storage.ts +324 -0
  543. package/botrun-c/web/lib/firebase-capacitor.ts +135 -0
  544. package/botrun-c/web/lib/firebase.ts +137 -0
  545. package/botrun-c/web/lib/firestore-auth.ts +61 -0
  546. package/botrun-c/web/lib/firestore-auth.ts.migration-bak +319 -0
  547. package/botrun-c/web/lib/image-extractor.ts +156 -0
  548. package/botrun-c/web/lib/path-utils.ts +48 -0
  549. package/botrun-c/web/lib/rehype-del-to-mark.ts +19 -0
  550. package/botrun-c/web/lib/upload-service.ts +326 -0
  551. package/botrun-c/web/lib/utils.ts +6 -0
  552. package/botrun-c/web/next.config.js +49 -0
  553. package/botrun-c/web/package-lock.json +6711 -0
  554. package/botrun-c/web/package.json +63 -0
  555. package/botrun-c/web/postcss.config.js +6 -0
  556. package/botrun-c/web/public/apple_icon.png +0 -0
  557. package/botrun-c/web/public/google_icon.png +0 -0
  558. package/botrun-c/web/public/logo-48-small.png +0 -0
  559. package/botrun-c/web/public/logo-48.png +0 -0
  560. package/botrun-c/web/scripts/generate-version.js +36 -0
  561. package/botrun-c/web/scripts/update-build-version.js +41 -0
  562. package/botrun-c/web/tailwind.config.ts +80 -0
  563. package/botrun-c/web/test-welcome-message.mjs +115 -0
  564. package/botrun-c/web/tsconfig.json +42 -0
  565. package/botrun-c/web/types/agent.ts +6 -0
  566. package/botrun-c/web/types/attachment.ts +39 -0
  567. package/botrun-c/web/types/conversation.ts +68 -0
  568. package/botrun-c/web/types/flutter.d.ts +6 -0
  569. package/botrun-c/web/types/project.ts +41 -0
  570. package/botrun-c/web/types/skills.ts +13 -0
  571. package/botrun-c/web//350/250/272/346/226/267-Skills-/345/211/215/347/253/257/351/241/257/347/244/272/345/225/217/351/241/214.md +83 -0
  572. package/lib/prompt/prompts/zero-framework/fullstack.md +17 -0
  573. package/lib/prompt/prompts/zero-framework/segment.md +13 -0
  574. package/package.json +1 -1
@@ -0,0 +1,1146 @@
1
+ # 技能分享功能:設計規格
2
+
3
+ ## 🎯 系統架構概述
4
+
5
+ 技能分享功能允許使用者將自己建立的技能透過簡短的分享 ID 分享給其他人,其他人可以透過輸入分享 ID 將技能 clone 到自己的技能集中。
6
+
7
+ ### 核心設計理念
8
+
9
+ 1. **確定性 Share ID**:使用 MD5(userId + skillName) 作為分享識別碼,支援智慧覆寫
10
+ 2. **Clone 而非 Reference**:匯入的技能是完整複製,使用者可自由修改
11
+ 3. **Firestore 儲存**:分享記錄儲存在 Firestore,便於管理和查詢
12
+ 4. **Shadcn UI 組件**:使用 Dialog、DropdownMenu、Input 等原生組件實現 RWD
13
+
14
+ ---
15
+
16
+ ## [D-1] Share ID 設計
17
+
18
+ ### 設計說明
19
+
20
+ **採用 MD5(userId + skillName) 作為 Share ID**,理由如下:
21
+
22
+ 1. **確定性(Deterministic)**:同一使用者的同一技能名稱 → 永遠產生相同的 shareId
23
+ 2. **自動覆寫**:修改技能內容後再次分享 → 自動覆寫 Firestore(無需查詢)
24
+ 3. **自動版本分支**:改變技能名稱 → 產生新的 shareId(新分享記錄)
25
+ 4. **查詢效能極佳**:使用 document ID 查詢,O(1) 時間複雜度
26
+ 5. **無需額外索引**:不需要建立 `(ownerId, skillName)` 複合索引
27
+
28
+ ### Share ID 生成邏輯
29
+
30
+ ```typescript
31
+ // api/src/utils/share-id.ts
32
+ import crypto from 'crypto';
33
+
34
+ export function generateShareId(userId: string, skillName: string): string {
35
+ // MD5 完全支援 UTF-8(包含中文)
36
+ const content = `${userId}:${skillName}`;
37
+ return crypto.createHash('md5').update(content, 'utf-8').digest('hex');
38
+ }
39
+
40
+ // 範例:
41
+ // userId = "abc123"
42
+ // skillName = "孵化技能"
43
+ // shareId = "7a3f8c2e1b4d9f6a5e8c3d2a1f9b7e4c"(32 字元 hex)
44
+ ```
45
+
46
+ ### 中文支援
47
+
48
+ **問題**:中文可以做 MD5 嗎?
49
+ **答案**:✅ 完全支援
50
+
51
+ ```typescript
52
+ // Node.js crypto 模組自動處理 UTF-8 編碼
53
+ crypto.createHash('md5').update('孵化技能', 'utf-8').digest('hex');
54
+ // 輸出: "7a3f8c2e1b4d9f6a5e8c3d2a1f9b7e4c"
55
+ ```
56
+
57
+ ### 分享行為
58
+
59
+ | 操作 | shareId | Firestore 行為 | 說明 |
60
+ |------|---------|---------------|------|
61
+ | 首次分享「技能 A」 | `md5(user:技能A)` | 建立新記錄 | `createdAt` = 當前時間 |
62
+ | 修改內容後再分享「技能 A」 | `md5(user:技能A)` ✅ 相同 | **覆寫**現有記錄 | `createdAt` 保持不變,`updatedAt` 更新 |
63
+ | 重新命名為「技能 B」後分享 | `md5(user:技能B)` ❌ 不同 | 建立新記錄 | 產生新的 shareId |
64
+
65
+ ### 優點總結
66
+
67
+ 1. **智慧覆寫**:技能內容更新時,分享連結不變(使用者體驗佳)
68
+ 2. **改名隔離**:技能改名視為新技能,產生新分享(合理)
69
+ 3. **效能最佳**:無需查詢「是否已分享」,直接用 MD5 當 document ID
70
+ 4. **儲存節省**:同一技能只有一筆記錄(除非改名)
71
+
72
+ ---
73
+
74
+ ## [D-2] 資料結構設計
75
+
76
+ ### 為什麼需要 `sharedSkills` Collection?
77
+
78
+ **現有技能儲存方式**:
79
+ - 技能存放在檔案系統:`/mnt/data/{userId}/.claude/skills/{skillId}/SKILL.md`
80
+ - 每個使用者只能存取自己的檔案(私有隔離)
81
+ - **沒有** Firestore 的 `skills` collection
82
+
83
+ **跨使用者分享的挑戰**:
84
+ - 使用者 A 的技能檔案位於 `/mnt/data/user_A/.claude/skills/孵化技能/SKILL.md`
85
+ - 使用者 B 無法直接存取使用者 A 的檔案系統
86
+ - 需要一個**公開的中介儲存層**來傳遞技能內容
87
+
88
+ **解決方案:Firestore `sharedSkills` Collection**
89
+ - 作為技能內容的「公開快照」儲存
90
+ - 任何人都可透過 shareId 讀取(無需知道原擁有者)
91
+ - 使用者 B 讀取後 clone 到自己的 GCS 儲存空間
92
+
93
+ **技能儲存架構**:
94
+ ```
95
+ 使用者 A 的技能(原始):
96
+ GCS: botrun-c-uploads/user_A/.claude/skills/孵化技能/SKILL.md
97
+ 掛載: /mnt/data/user_A/.claude/skills/孵化技能/SKILL.md
98
+
99
+ 分享快照(Firestore):
100
+ Collection: sharedSkills
101
+ Document ID: md5(user_A:孵化技能)
102
+ Field: skillContent = "完整 SKILL.md 內容"
103
+
104
+ 使用者 B 匯入(clone):
105
+ GCS: botrun-c-uploads/user_B/.claude/skills/孵化技能/SKILL.md
106
+ 掛載: /mnt/data/user_B/.claude/skills/孵化技能/SKILL.md
107
+ ```
108
+
109
+ ### Firestore Collection: `sharedSkills`
110
+
111
+ ```typescript
112
+ // sharedSkills/{shareId} where shareId = md5(userId + skillName)
113
+ interface SharedSkill {
114
+ shareId: string; // MD5 hash(即 document ID)
115
+ ownerId: string; // 分享者 userId(用於審計和防濫用)
116
+ skillName: string; // 技能名稱
117
+ skillDescription: string; // 技能描述
118
+ skillIcon: string; // 技能 icon(emoji)
119
+ skillContent: string; // 完整的 SKILL.md 內容(YAML + Markdown)
120
+ createdAt: Timestamp; // 首次分享時間
121
+ updatedAt: Timestamp; // 最後更新時間
122
+ }
123
+ ```
124
+
125
+ **欄位用途說明**:
126
+ - ✅ `shareId`:MD5 hash 作為 document ID,支援自動覆寫
127
+ - ✅ `ownerId`:記錄分享者(用於審計、防濫用),但**不用於查詢**
128
+ - ✅ `createdAt`:首次分享時間(覆寫時不變)
129
+ - ✅ `updatedAt`:內容更新時間(每次覆寫都更新)
130
+
131
+ **移除的欄位**:
132
+ - ❌ `downloadCount`:不統計下載次數
133
+ - ❌ `shareUrl`:現階段不使用完整 URL
134
+ - ❌ `expiresAt`:現階段不實作過期機制
135
+
136
+ ### Firestore 索引
137
+
138
+ ```typescript
139
+ // 單一欄位索引:shareId(作為 document ID,自動索引)
140
+ // 無需額外建立複合索引
141
+ ```
142
+
143
+ ---
144
+
145
+ ## [D-3] API 設計
146
+
147
+ ### 3.1 建立分享
148
+
149
+ **Endpoint**: `POST /api/skills/share`
150
+
151
+ **Request Body**:
152
+ ```typescript
153
+ {
154
+ skillId: string; // 要分享的技能 ID
155
+ }
156
+ ```
157
+
158
+ **Response**:
159
+ ```typescript
160
+ {
161
+ success: true;
162
+ shareId: string; // MD5 hash(32 字元 hex)
163
+ }
164
+ ```
165
+
166
+ **實作邏輯**:
167
+ ```typescript
168
+ // api/src/routes/skills.ts
169
+ import { getFirestore, FieldValue } from 'firebase-admin/firestore';
170
+ import { generateShareId } from '../utils/share-id.js';
171
+
172
+ router.post('/share', async (req, res) => {
173
+ const { skillId } = req.body;
174
+ const userId = req.user.uid;
175
+
176
+ // 1. 檢查技能是否存在且屬於使用者
177
+ const skillsRepo = new SkillsRepository(userId);
178
+ const skillExists = await skillsRepo.exists(skillId);
179
+
180
+ if (!skillExists) {
181
+ return res.status(404).json({
182
+ success: false,
183
+ error: '找不到此技能'
184
+ });
185
+ }
186
+
187
+ // 2. 讀取完整技能內容
188
+ const skillContent = await skillsRepo.getSkillContent(skillId);
189
+ const skillName = skillContent.metadata.name;
190
+
191
+ // 3. 生成 shareId = md5(userId + skillName)
192
+ const shareId = generateShareId(userId, skillName);
193
+
194
+ // 4. 檢查是否已存在(用於保留 createdAt)
195
+ const db = getFirestore();
196
+ const existingDoc = await db.collection('sharedSkills').doc(shareId).get();
197
+
198
+ // 5. 儲存到 Firestore(覆寫模式)
199
+ await db.collection('sharedSkills').doc(shareId).set({
200
+ shareId,
201
+ ownerId: userId,
202
+ skillName,
203
+ skillDescription: skillContent.metadata.description || '',
204
+ skillIcon: skillContent.metadata.icon || '🔧',
205
+ skillContent: skillContent.rawContent,
206
+ createdAt: existingDoc.exists
207
+ ? existingDoc.data()!.createdAt
208
+ : FieldValue.serverTimestamp(),
209
+ updatedAt: FieldValue.serverTimestamp()
210
+ });
211
+
212
+ return res.json({
213
+ success: true,
214
+ shareId
215
+ });
216
+ });
217
+ ```
218
+
219
+ ### 3.2 取得分享內容(預覽用)
220
+
221
+ **Endpoint**: `GET /api/skills/share/:shareId`
222
+
223
+ **用途**:供前端在匯入前預覽技能資訊
224
+
225
+ **Response**:
226
+ ```typescript
227
+ {
228
+ success: true;
229
+ shareId: string;
230
+ skillName: string;
231
+ skillDescription: string;
232
+ skillIcon: string;
233
+ createdAt: string; // ISO 8601 格式
234
+ }
235
+ ```
236
+
237
+ **實作邏輯**:
238
+ ```typescript
239
+ router.get('/share/:shareId', async (req, res) => {
240
+ const { shareId } = req.params;
241
+
242
+ // 從 Firestore 取得分享記錄
243
+ const db = getFirestore();
244
+ const doc = await db.collection('sharedSkills').doc(shareId).get();
245
+
246
+ if (!doc.exists) {
247
+ return res.status(404).json({
248
+ success: false,
249
+ error: '找不到此分享技能'
250
+ });
251
+ }
252
+
253
+ const data = doc.data()!;
254
+
255
+ return res.json({
256
+ success: true,
257
+ shareId: data.shareId,
258
+ skillName: data.skillName,
259
+ skillDescription: data.skillDescription,
260
+ skillIcon: data.skillIcon,
261
+ createdAt: data.createdAt.toDate().toISOString()
262
+ });
263
+ });
264
+ ```
265
+
266
+ ### 3.3 匯入分享技能
267
+
268
+ **Endpoint**: `POST /api/skills/import/:shareId`
269
+
270
+ **用途**:將分享的技能 clone 到使用者自己的 GCS 儲存空間
271
+
272
+ **Request Body**:
273
+ ```typescript
274
+ {
275
+ overwrite?: boolean; // 可選,true = 覆寫現有技能,預設 false
276
+ }
277
+ ```
278
+
279
+ **Response**:
280
+ ```typescript
281
+ // 成功匯入
282
+ {
283
+ success: true;
284
+ skillId: string; // 新建立的技能 ID
285
+ skillName: string; // 技能名稱
286
+ }
287
+
288
+ // 名稱衝突(需要使用者確認)
289
+ {
290
+ success: false;
291
+ conflict: true;
292
+ skillName: string; // 衝突的技能名稱
293
+ error: "技能名稱已存在"
294
+ }
295
+ ```
296
+
297
+ **實作邏輯**:
298
+ ```typescript
299
+ router.post('/import/:shareId', async (req, res) => {
300
+ const { shareId } = req.params;
301
+ const { overwrite = false } = req.body;
302
+ const userId = req.user.uid;
303
+
304
+ // 1. 從 Firestore 取得分享內容
305
+ const db = getFirestore();
306
+ const doc = await db.collection('sharedSkills').doc(shareId).get();
307
+
308
+ if (!doc.exists) {
309
+ return res.status(404).json({
310
+ success: false,
311
+ error: '找不到此分享技能'
312
+ });
313
+ }
314
+
315
+ const sharedData = doc.data()!;
316
+
317
+ // 2. 檢查名稱衝突
318
+ const skillsRepo = new SkillsRepository(userId);
319
+ const nameExists = await skillsRepo.exists(sharedData.skillName);
320
+
321
+ if (nameExists && !overwrite) {
322
+ // 名稱衝突,返回 conflict 狀態,讓前端顯示確認對話框
323
+ return res.status(409).json({
324
+ success: false,
325
+ conflict: true,
326
+ skillName: sharedData.skillName,
327
+ error: '技能名稱已存在'
328
+ });
329
+ }
330
+
331
+ // 3. 處理覆寫
332
+ if (nameExists && overwrite) {
333
+ // 使用者選擇覆寫:刪除舊技能
334
+ await skillsRepo.deleteSkill(sharedData.skillName);
335
+ }
336
+
337
+ // 4. 建立新技能(clone 完整內容到使用者的 GCS)
338
+ const newSkillId = sharedData.skillName.replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g, '-');
339
+ await skillsRepo.createSkill(newSkillId, sharedData.skillContent);
340
+
341
+ // 實際寫入路徑:
342
+ // 檔案系統: /mnt/data/{userId}/.claude/skills/{newSkillId}/SKILL.md
343
+ // 實際儲存: GCS bucket "botrun-c-uploads" (透過 GCS FUSE 掛載)
344
+
345
+ return res.json({
346
+ success: true,
347
+ skillId: newSkillId,
348
+ skillName: sharedData.skillName
349
+ });
350
+ });
351
+ ```
352
+
353
+ ---
354
+
355
+ ## [D-4] 前端 UI 設計
356
+
357
+ ### 4.0 跨平台剪貼簿支援
358
+
359
+ **依賴安裝**(專案尚未安裝):
360
+ ```bash
361
+ pnpm add @capacitor/clipboard
362
+ npx cap sync
363
+ ```
364
+
365
+ **使用方式**:
366
+ ```typescript
367
+ import { Clipboard } from '@capacitor/clipboard';
368
+
369
+ // 複製到剪貼簿(支援 Web/iOS/Android)
370
+ await Clipboard.write({ string: shareId });
371
+ ```
372
+
373
+ **參考文件**:[Capacitor Clipboard API](https://capacitorjs.com/docs/apis/clipboard)
374
+
375
+ ### 4.1 分享按鈕(Share Button)
376
+
377
+ 在技能卡片上加入分享按鈕,點擊後顯示分享 Dialog。
378
+
379
+ **位置**: `web/components/skills-manager-drawer.tsx`
380
+
381
+ ```tsx
382
+ // 在 SortableSkillItem 中加入分享按鈕
383
+ import { Share2 } from "lucide-react";
384
+
385
+ function SortableSkillItem({ skill, onEdit, onDelete, onShare, isDraggable }) {
386
+ return (
387
+ <div className="flex items-start gap-3 p-4 rounded-lg border">
388
+ {/* ... 現有內容 ... */}
389
+
390
+ <div className="flex gap-2 shrink-0">
391
+ {/* 分享按鈕 - 只有使用者技能才顯示 */}
392
+ {skill.source === "user" && (
393
+ <Button
394
+ variant="ghost"
395
+ size="icon"
396
+ onClick={() => onShare(skill)}
397
+ title="分享技能"
398
+ >
399
+ <Share2 className="h-4 w-4" />
400
+ </Button>
401
+ )}
402
+
403
+ {/* 編輯/查看按鈕 */}
404
+ <Button variant="ghost" size="icon" onClick={() => onEdit(skill)}>
405
+ {skill.editable ? <Pencil /> : <Eye />}
406
+ </Button>
407
+
408
+ {/* 刪除按鈕 */}
409
+ {skill.editable && (
410
+ <Button variant="ghost" size="icon" onClick={() => onDelete(skill)}>
411
+ <Trash2 />
412
+ </Button>
413
+ )}
414
+ </div>
415
+ </div>
416
+ );
417
+ }
418
+ ```
419
+
420
+ ### 4.2 分享 Dialog (ShareSkillDialog)
421
+
422
+ **新檔案**: `web/components/share-skill-dialog.tsx`
423
+
424
+ ```tsx
425
+ "use client";
426
+
427
+ import { useState } from "react";
428
+ import {
429
+ Dialog,
430
+ DialogContent,
431
+ DialogDescription,
432
+ DialogHeader,
433
+ DialogTitle,
434
+ } from "@/components/ui/dialog";
435
+ import { Button } from "@/components/ui/button";
436
+ import { Input } from "@/components/ui/input";
437
+ import { Label } from "@/components/ui/label";
438
+ import { Copy, Check, Loader2, AlertCircle } from "lucide-react";
439
+ import { Clipboard } from "@capacitor/clipboard";
440
+
441
+ interface ShareSkillDialogProps {
442
+ open: boolean;
443
+ onOpenChange: (open: boolean) => void;
444
+ skillId: string;
445
+ skillName: string;
446
+ }
447
+
448
+ export function ShareSkillDialog({
449
+ open,
450
+ onOpenChange,
451
+ skillId,
452
+ skillName
453
+ }: ShareSkillDialogProps) {
454
+ const [shareId, setShareId] = useState<string | null>(null);
455
+ const [isLoading, setIsLoading] = useState(false);
456
+ const [copied, setCopied] = useState(false);
457
+ const [error, setError] = useState<string | null>(null);
458
+
459
+ // 生成分享連結
460
+ const handleCreateShare = async () => {
461
+ setIsLoading(true);
462
+ setError(null);
463
+ try {
464
+ const response = await fetch('/api/skills/share', {
465
+ method: 'POST',
466
+ headers: { 'Content-Type': 'application/json' },
467
+ body: JSON.stringify({ skillId })
468
+ });
469
+
470
+ const data = await response.json();
471
+
472
+ if (!data.success) {
473
+ throw new Error(data.error || '建立失敗');
474
+ }
475
+
476
+ setShareId(data.shareId);
477
+ } catch (err: any) {
478
+ setError(err.message || '建立分享連結失敗');
479
+ } finally {
480
+ setIsLoading(false);
481
+ }
482
+ };
483
+
484
+ // 複製到剪貼簿(跨平台支援)
485
+ const handleCopy = async () => {
486
+ if (!shareId) return;
487
+
488
+ try {
489
+ await Clipboard.write({ string: shareId });
490
+ setCopied(true);
491
+ setTimeout(() => setCopied(false), 2000);
492
+ } catch (err) {
493
+ console.error('複製失敗:', err);
494
+ }
495
+ };
496
+
497
+ return (
498
+ <Dialog open={open} onOpenChange={onOpenChange}>
499
+ <DialogContent className="sm:max-w-[500px]">
500
+ <DialogHeader>
501
+ <DialogTitle>分享技能:{skillName}</DialogTitle>
502
+ <DialogDescription>
503
+ 建立分享 ID,其他人可以透過此 ID 將技能匯入到自己的技能集
504
+ </DialogDescription>
505
+ </DialogHeader>
506
+
507
+ <div className="space-y-4 py-4">
508
+ {!shareId ? (
509
+ <>
510
+ <Button
511
+ onClick={handleCreateShare}
512
+ disabled={isLoading}
513
+ className="w-full"
514
+ >
515
+ {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
516
+ 建立分享連結
517
+ </Button>
518
+
519
+ {/* 錯誤訊息 */}
520
+ {error && (
521
+ <div className="flex items-start gap-2 p-3 bg-destructive/10 text-destructive rounded-md text-sm">
522
+ <AlertCircle className="h-4 w-4 mt-0.5 shrink-0" />
523
+ <span>{error}</span>
524
+ </div>
525
+ )}
526
+ </>
527
+ ) : (
528
+ <div className="space-y-2">
529
+ <Label>分享 ID</Label>
530
+ <div className="flex gap-2">
531
+ <Input
532
+ value={shareId}
533
+ readOnly
534
+ className="font-mono text-xs"
535
+ />
536
+ <Button
537
+ variant="outline"
538
+ size="sm"
539
+ onClick={handleCopy}
540
+ className="shrink-0"
541
+ >
542
+ {copied ? (
543
+ <>
544
+ <Check className="h-4 w-4 mr-2" />
545
+ 已複製!
546
+ </>
547
+ ) : (
548
+ <>
549
+ <Copy className="h-4 w-4 mr-2" />
550
+ 複製
551
+ </>
552
+ )}
553
+ </Button>
554
+ </div>
555
+ <p className="text-sm text-muted-foreground">
556
+ 將此 ID 分享給其他人,他們可以在「匯入分享技能」中使用
557
+ </p>
558
+ </div>
559
+ )}
560
+ </div>
561
+ </DialogContent>
562
+ </Dialog>
563
+ );
564
+ }
565
+ ```
566
+
567
+ **設計重點**:
568
+ - ✅ 使用 `@capacitor/clipboard` 支援跨平台複製
569
+ - ✅ 移除 `toast`,改用按鈕文字和 icon 變化("複製" → "已複製!")
570
+ - ✅ 錯誤訊息內嵌顯示(不使用 toast)
571
+ - ✅ MD5 hash 完整顯示(32 字元 hex),字體使用 `font-mono text-xs`
572
+
573
+ ### 4.3 新增技能按鈕改為 DropdownMenu
574
+
575
+ **位置**: `web/components/skills-manager-drawer.tsx`
576
+
577
+ ```tsx
578
+ import {
579
+ DropdownMenu,
580
+ DropdownMenuContent,
581
+ DropdownMenuItem,
582
+ DropdownMenuTrigger,
583
+ } from "@/components/ui/dropdown-menu";
584
+ import { Plus, FileDown, ChevronDown } from "lucide-react";
585
+
586
+ // 原本的單一按鈕
587
+ // <Button onClick={onCreate}>
588
+ // <Plus className="h-4 w-4 mr-2" />
589
+ // 新增技能
590
+ // </Button>
591
+
592
+ // 改為 DropdownMenu
593
+ <DropdownMenu>
594
+ <DropdownMenuTrigger asChild>
595
+ <Button>
596
+ <Plus className="h-4 w-4 mr-2" />
597
+ 新增技能
598
+ <ChevronDown className="h-4 w-4 ml-2" />
599
+ </Button>
600
+ </DropdownMenuTrigger>
601
+ <DropdownMenuContent align="end">
602
+ <DropdownMenuItem onClick={onCreate}>
603
+ <Plus className="h-4 w-4 mr-2" />
604
+ 建立新技能
605
+ </DropdownMenuItem>
606
+ <DropdownMenuItem onClick={() => setImportDialogOpen(true)}>
607
+ <FileDown className="h-4 w-4 mr-2" />
608
+ 匯入分享技能
609
+ </DropdownMenuItem>
610
+ </DropdownMenuContent>
611
+ </DropdownMenu>
612
+ ```
613
+
614
+ ### 4.4 匯入技能 Dialog (ImportSkillDialog)
615
+
616
+ **新檔案**: `web/components/import-skill-dialog.tsx`
617
+
618
+ ```tsx
619
+ "use client";
620
+
621
+ import { useState } from "react";
622
+ import {
623
+ Dialog,
624
+ DialogContent,
625
+ DialogDescription,
626
+ DialogHeader,
627
+ DialogTitle,
628
+ DialogFooter,
629
+ } from "@/components/ui/dialog";
630
+ import {
631
+ AlertDialog,
632
+ AlertDialogAction,
633
+ AlertDialogCancel,
634
+ AlertDialogContent,
635
+ AlertDialogDescription,
636
+ AlertDialogFooter,
637
+ AlertDialogHeader,
638
+ AlertDialogTitle,
639
+ } from "@/components/ui/alert-dialog";
640
+ import { Button } from "@/components/ui/button";
641
+ import { Input } from "@/components/ui/input";
642
+ import { Label } from "@/components/ui/label";
643
+ import { Loader2, AlertCircle } from "lucide-react";
644
+
645
+ interface ImportSkillDialogProps {
646
+ open: boolean;
647
+ onOpenChange: (open: boolean) => void;
648
+ onSuccess: () => void; // 匯入成功後重新載入技能列表
649
+ }
650
+
651
+ interface SkillPreview {
652
+ shareId: string;
653
+ skillName: string;
654
+ skillDescription: string;
655
+ skillIcon: string;
656
+ createdAt: string;
657
+ }
658
+
659
+ export function ImportSkillDialog({
660
+ open,
661
+ onOpenChange,
662
+ onSuccess
663
+ }: ImportSkillDialogProps) {
664
+ const [shareId, setShareId] = useState("");
665
+ const [isLoadingPreview, setIsLoadingPreview] = useState(false);
666
+ const [isImporting, setIsImporting] = useState(false);
667
+ const [preview, setPreview] = useState<SkillPreview | null>(null);
668
+ const [error, setError] = useState<string | null>(null);
669
+
670
+ // 名稱衝突確認對話框狀態
671
+ const [showConflictDialog, setShowConflictDialog] = useState(false);
672
+ const [conflictSkillName, setConflictSkillName] = useState<string | null>(null);
673
+
674
+ // 預覽分享技能
675
+ const handlePreview = async () => {
676
+ if (!shareId.trim()) {
677
+ setError('請輸入分享 ID');
678
+ return;
679
+ }
680
+
681
+ setIsLoadingPreview(true);
682
+ setError(null);
683
+ setPreview(null);
684
+
685
+ try {
686
+ const response = await fetch(`/api/skills/share/${shareId.trim()}`);
687
+ const data = await response.json();
688
+
689
+ if (!data.success) {
690
+ throw new Error(data.error || '找不到此分享技能');
691
+ }
692
+
693
+ setPreview(data);
694
+ } catch (err: any) {
695
+ setError(err.message || '載入失敗');
696
+ } finally {
697
+ setIsLoadingPreview(false);
698
+ }
699
+ };
700
+
701
+ // 確認匯入
702
+ const handleImport = async (overwrite?: boolean) => {
703
+ setIsImporting(true);
704
+ setError(null);
705
+
706
+ try {
707
+ const response = await fetch(`/api/skills/import/${shareId.trim()}`, {
708
+ method: 'POST',
709
+ headers: { 'Content-Type': 'application/json' },
710
+ body: JSON.stringify({ overwrite })
711
+ });
712
+
713
+ const data = await response.json();
714
+
715
+ // 檢查名稱衝突
716
+ if (response.status === 409 && data.conflict) {
717
+ setConflictSkillName(data.skillName);
718
+ setShowConflictDialog(true);
719
+ setIsImporting(false);
720
+ return;
721
+ }
722
+
723
+ if (!data.success) {
724
+ throw new Error(data.error || '匯入失敗');
725
+ }
726
+
727
+ // 成功:關閉 Dialog 並重新載入列表
728
+ onOpenChange(false);
729
+ onSuccess();
730
+ } catch (err: any) {
731
+ setError(err.message || '匯入失敗');
732
+ } finally {
733
+ setIsImporting(false);
734
+ }
735
+ };
736
+
737
+ // 處理衝突選擇
738
+ const handleConflictChoice = async (choice: 'overwrite' | 'keep-both' | 'cancel') => {
739
+ setShowConflictDialog(false);
740
+
741
+ if (choice === 'cancel') {
742
+ return;
743
+ }
744
+
745
+ if (choice === 'overwrite') {
746
+ await handleImport(true); // overwrite = true
747
+ } else {
748
+ // keep-both: 不傳 overwrite,後端自動加數字後綴
749
+ await handleImport(false);
750
+ }
751
+ };
752
+
753
+ return (
754
+ <>
755
+ <Dialog open={open} onOpenChange={onOpenChange}>
756
+ <DialogContent className="sm:max-w-[500px]">
757
+ <DialogHeader>
758
+ <DialogTitle>匯入分享技能</DialogTitle>
759
+ <DialogDescription>
760
+ 輸入分享 ID 以匯入其他人分享的技能
761
+ </DialogDescription>
762
+ </DialogHeader>
763
+
764
+ <div className="space-y-4 py-4">
765
+ {/* 輸入分享 ID */}
766
+ <div className="space-y-2">
767
+ <Label htmlFor="shareId">分享 ID</Label>
768
+ <div className="flex gap-2">
769
+ <Input
770
+ id="shareId"
771
+ placeholder="貼上 32 字元的分享 ID"
772
+ value={shareId}
773
+ onChange={(e) => {
774
+ setShareId(e.target.value);
775
+ setPreview(null);
776
+ setError(null);
777
+ }}
778
+ className="font-mono text-xs"
779
+ />
780
+ <Button
781
+ variant="outline"
782
+ onClick={handlePreview}
783
+ disabled={isLoadingPreview || !shareId.trim()}
784
+ className="shrink-0"
785
+ >
786
+ {isLoadingPreview && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
787
+ 預覽
788
+ </Button>
789
+ </div>
790
+ </div>
791
+
792
+ {/* 錯誤訊息 */}
793
+ {error && (
794
+ <div className="flex items-start gap-2 p-3 bg-destructive/10 text-destructive rounded-md text-sm">
795
+ <AlertCircle className="h-4 w-4 mt-0.5 shrink-0" />
796
+ <span>{error}</span>
797
+ </div>
798
+ )}
799
+
800
+ {/* 預覽區域 */}
801
+ {preview && (
802
+ <div className="rounded-lg border p-4 space-y-2 bg-muted/50">
803
+ <div className="flex items-center gap-2">
804
+ <span className="text-2xl">{preview.skillIcon}</span>
805
+ <h3 className="font-semibold">{preview.skillName}</h3>
806
+ </div>
807
+ <p className="text-sm text-muted-foreground">
808
+ {preview.skillDescription}
809
+ </p>
810
+ <p className="text-xs text-muted-foreground">
811
+ 分享時間:{new Date(preview.createdAt).toLocaleDateString('zh-TW')}
812
+ </p>
813
+ </div>
814
+ )}
815
+ </div>
816
+
817
+ <DialogFooter>
818
+ <Button variant="outline" onClick={() => onOpenChange(false)}>
819
+ 取消
820
+ </Button>
821
+ <Button
822
+ onClick={() => handleImport()}
823
+ disabled={!preview || isImporting}
824
+ >
825
+ {isImporting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
826
+ 確認匯入
827
+ </Button>
828
+ </DialogFooter>
829
+ </DialogContent>
830
+ </Dialog>
831
+
832
+ {/* 名稱衝突確認對話框 */}
833
+ <AlertDialog open={showConflictDialog} onOpenChange={setShowConflictDialog}>
834
+ <AlertDialogContent>
835
+ <AlertDialogHeader>
836
+ <AlertDialogTitle>技能名稱已存在</AlertDialogTitle>
837
+ <AlertDialogDescription>
838
+ 您已經有一個名為「<strong>{conflictSkillName}</strong>」的技能。請選擇如何處理:
839
+ </AlertDialogDescription>
840
+ </AlertDialogHeader>
841
+ <AlertDialogFooter>
842
+ <AlertDialogCancel onClick={() => handleConflictChoice('cancel')}>
843
+ 取消匯入
844
+ </AlertDialogCancel>
845
+ <AlertDialogAction
846
+ onClick={() => handleConflictChoice('overwrite')}
847
+ disabled={isImporting}
848
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
849
+ >
850
+ {isImporting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
851
+ 覆寫現有技能
852
+ </AlertDialogAction>
853
+ </AlertDialogFooter>
854
+ </AlertDialogContent>
855
+ </AlertDialog>
856
+ </>
857
+ );
858
+ }
859
+ ```
860
+
861
+ **設計重點**:
862
+ - ✅ 移除所有 `toast`,錯誤訊息內嵌顯示
863
+ - ✅ 匯入成功後直接關閉 Dialog,技能列表自動重新整理(使用者會看到新技能)
864
+ - ✅ 顯示「分享時間」(`createdAt`)
865
+ - ✅ MD5 hash 輸入框使用 `font-mono text-xs`(32 字元)
866
+ - ✅ 預覽卡片增加背景色區分(`bg-muted/50`)
867
+ - ✅ **名稱衝突確認**:偵測到衝突時顯示 AlertDialog,提供兩個選項:
868
+ - **覆寫現有技能**(紅色按鈕,警示性)
869
+ - **取消匯入**
870
+
871
+ ---
872
+
873
+ ## [D-5] RWD 響應式設計
874
+
875
+ ### Shadcn Dialog 原生 RWD 支援
876
+
877
+ Shadcn 的 Dialog 組件已內建 RWD 支援(參考:https://ui.shadcn.com/docs/components/dialog):
878
+
879
+ ```tsx
880
+ // DialogContent 預設樣式已包含響應式設計
881
+ <DialogContent className="sm:max-w-[425px]">
882
+ {/*
883
+ - Mobile: 全螢幕顯示(max-w-full)
884
+ - Desktop: 固定寬度 425px(sm:max-w-[425px])
885
+ */}
886
+ </DialogContent>
887
+ ```
888
+
889
+ ### DropdownMenu 觸控優化
890
+
891
+ 參考 Shadcn DropdownMenu 文件:https://ui.shadcn.com/docs/components/dropdown-menu
892
+
893
+ ```tsx
894
+ // DropdownMenuContent 自動處理觸控和鍵盤導航
895
+ <DropdownMenuContent align="end">
896
+ {/*
897
+ - 自動調整位置避免超出視窗
898
+ - 觸控裝置支援
899
+ - 鍵盤導航支援(箭頭鍵、Enter、Escape)
900
+ */}
901
+ </DropdownMenuContent>
902
+ ```
903
+
904
+ ---
905
+
906
+ ## [D-6] 錯誤處理與邊界情況
907
+
908
+ ### 6.1 Share ID 不存在
909
+
910
+ ```typescript
911
+ // API Response
912
+ {
913
+ success: false,
914
+ error: "找不到此分享技能"
915
+ }
916
+
917
+ // 前端處理:內嵌錯誤訊息(不使用 toast)
918
+ <div className="flex items-start gap-2 p-3 bg-destructive/10 text-destructive rounded-md text-sm">
919
+ <AlertCircle className="h-4 w-4 mt-0.5 shrink-0" />
920
+ <span>找不到此分享技能</span>
921
+ </div>
922
+ ```
923
+
924
+ ### 6.2 技能名稱衝突
925
+
926
+ 當匯入的技能名稱與現有技能重複時,顯示確認對話框讓使用者選擇:
927
+
928
+ **流程**:
929
+ 1. 使用者點擊「確認匯入」
930
+ 2. 後端檢測到名稱衝突,返回 409 狀態碼和 `conflict: true`
931
+ 3. 前端顯示 AlertDialog,提供兩個選項:
932
+ - **覆寫現有技能**:刪除舊技能,匯入新技能(使用原始名稱)
933
+ - **取消匯入**:放棄匯入操作
934
+
935
+ **後端處理邏輯**:
936
+ ```typescript
937
+ // api/src/routes/skills.ts
938
+
939
+ // 1. 檢測衝突
940
+ const nameExists = await skillsRepo.exists(sharedData.skillName);
941
+
942
+ if (nameExists && !overwrite) {
943
+ return res.status(409).json({
944
+ success: false,
945
+ conflict: true,
946
+ skillName: sharedData.skillName,
947
+ error: '技能名稱已存在'
948
+ });
949
+ }
950
+
951
+ // 2. 處理覆寫
952
+ if (nameExists && overwrite) {
953
+ await skillsRepo.deleteSkill(sharedData.skillName);
954
+ }
955
+ ```
956
+
957
+ **前端處理邏輯**:
958
+ ```tsx
959
+ // web/components/import-skill-dialog.tsx
960
+
961
+ const handleImport = async (overwrite: boolean = false) => {
962
+ const response = await fetch(`/api/skills/import/${shareId}`, {
963
+ method: 'POST',
964
+ headers: { 'Content-Type': 'application/json' },
965
+ body: JSON.stringify({ overwrite })
966
+ });
967
+
968
+ // 檢查衝突
969
+ if (response.status === 409 && data.conflict) {
970
+ setConflictSkillName(data.skillName);
971
+ setShowConflictDialog(true); // 顯示確認對話框
972
+ return;
973
+ }
974
+ };
975
+
976
+ // 使用者選擇
977
+ const handleConflictChoice = async (choice: 'overwrite' | 'cancel') => {
978
+ if (choice === 'overwrite') {
979
+ await handleImport(true); // 覆寫
980
+ }
981
+ // cancel: 不執行任何動作
982
+ };
983
+ ```
984
+
985
+ ### 6.3 權限檢查
986
+
987
+ ```typescript
988
+ // 前端:只有使用者技能(source === "user")才顯示分享按鈕
989
+ {skill.source === "user" && (
990
+ <Button onClick={() => onShare(skill)} title="分享技能">
991
+ <Share2 className="h-4 w-4" />
992
+ </Button>
993
+ )}
994
+
995
+ // 後端:檢查技能是否存在且屬於使用者
996
+ const skillsRepo = new SkillsRepository(userId);
997
+ const skillExists = await skillsRepo.exists(skillId);
998
+
999
+ if (!skillExists) {
1000
+ return res.status(404).json({
1001
+ success: false,
1002
+ error: '找不到此技能'
1003
+ });
1004
+ }
1005
+ ```
1006
+
1007
+ ---
1008
+
1009
+ ## [D-7] 安全性考量
1010
+
1011
+ ### 7.1 防止 Share ID 爆破攻擊
1012
+
1013
+ ```typescript
1014
+ // Rate limiting(使用 express-rate-limit)
1015
+ import rateLimit from 'express-rate-limit';
1016
+
1017
+ const shareLimiter = rateLimit({
1018
+ windowMs: 15 * 60 * 1000, // 15 分鐘
1019
+ max: 10, // 最多 10 次請求
1020
+ message: '請求過於頻繁,請稍後再試'
1021
+ });
1022
+
1023
+ router.get('/share/:shareId', shareLimiter, async (req, res) => {
1024
+ // ...
1025
+ });
1026
+ ```
1027
+
1028
+ ### 7.2 內容驗證
1029
+
1030
+ ```typescript
1031
+ // 匯入前驗證 SKILL.md 內容
1032
+ function validateSkillContent(content: string): boolean {
1033
+ // 檢查是否包含惡意程式碼
1034
+ // 檢查檔案大小限制(例如:< 1MB)
1035
+ // 檢查 YAML frontmatter 格式
1036
+ return true;
1037
+ }
1038
+ ```
1039
+
1040
+ ### 7.3 私有技能保護
1041
+
1042
+ ```typescript
1043
+ // 使用者可選擇是否公開分享(未來功能)
1044
+ interface SharedSkill {
1045
+ isPublic: boolean; // 預設 true
1046
+ allowedUsers?: string[]; // 允許存取的使用者清單(私有分享)
1047
+ }
1048
+ ```
1049
+
1050
+ ---
1051
+
1052
+ ## [D-8] 效能優化
1053
+
1054
+ ### 8.1 MD5 生成效能
1055
+
1056
+ ```typescript
1057
+ // Node.js crypto.createHash('md5') 效能極佳
1058
+ import crypto from 'crypto';
1059
+ const shareId = crypto.createHash('md5')
1060
+ .update(`${userId}:${skillName}`, 'utf-8')
1061
+ .digest('hex'); // ~5μs
1062
+ ```
1063
+
1064
+ ### 8.2 Firestore 寫入優化(覆寫模式)
1065
+
1066
+ ```typescript
1067
+ // 使用確定性 shareId,直接覆寫(無需先查詢是否存在)
1068
+ const shareId = generateShareId(userId, skillName);
1069
+
1070
+ // 僅需一次查詢(用於判斷 createdAt)
1071
+ const existingDoc = await db.collection('sharedSkills').doc(shareId).get();
1072
+
1073
+ // 直接寫入(覆寫)
1074
+ await db.collection('sharedSkills').doc(shareId).set({...});
1075
+ ```
1076
+
1077
+ **效能優勢**:
1078
+ - ✅ 無需查詢「是否已分享過」
1079
+ - ✅ 無需處理碰撞重試邏輯
1080
+ - ✅ document ID 查詢是 Firestore 最快的查詢方式(O(1))
1081
+
1082
+ ### 8.3 前端快取策略(未來優化)
1083
+
1084
+ ```typescript
1085
+ // 可考慮在前端快取已預覽過的分享技能
1086
+ const [previewCache, setPreviewCache] = useState<Map<string, SkillPreview>>(new Map());
1087
+
1088
+ // 預覽前先檢查快取
1089
+ if (previewCache.has(shareId)) {
1090
+ setPreview(previewCache.get(shareId)!);
1091
+ return;
1092
+ }
1093
+ ```
1094
+
1095
+ ---
1096
+
1097
+ ## [D-9] 未來擴展功能
1098
+
1099
+ ### 9.1 技能市集(Skill Marketplace)
1100
+
1101
+ ```typescript
1102
+ // 新增欄位支援市集功能
1103
+ interface SharedSkill {
1104
+ // ... 現有欄位 ...
1105
+
1106
+ // 市集功能
1107
+ tags?: string[]; // 技能標籤(例如:["productivity", "automation"])
1108
+ rating?: number; // 評分(1-5)
1109
+ reviews?: number; // 評論數量
1110
+ featured?: boolean; // 是否為精選技能
1111
+ thumbnailUrl?: string; // 縮圖 URL
1112
+ }
1113
+ ```
1114
+
1115
+ ### 9.2 完整分享 URL
1116
+
1117
+ ```typescript
1118
+ // 支援 QR Code 和完整 URL 分享
1119
+ const shareUrl = `https://botrun.ai/skills/share/${shareId}`;
1120
+
1121
+ // 在分享 Dialog 中顯示 QR Code
1122
+ import QRCode from 'qrcode';
1123
+
1124
+ const qrCodeDataUrl = await QRCode.toDataURL(shareUrl);
1125
+ ```
1126
+
1127
+ ### 9.3 版本控制
1128
+
1129
+ ```typescript
1130
+ // 支援技能更新和版本追蹤
1131
+ interface SharedSkill {
1132
+ version?: string; // 版本號(例如:"1.0.0")
1133
+ previousVersions?: string[]; // 歷史版本的 shareId
1134
+ }
1135
+ ```
1136
+
1137
+ ---
1138
+
1139
+ ## 📚 參考資料
1140
+
1141
+ - [Capacitor Clipboard API](https://capacitorjs.com/docs/apis/clipboard) - 跨平台剪貼簿官方文件
1142
+ - [Shadcn UI Dialog](https://ui.shadcn.com/docs/components/dialog) - Dialog 元件文件
1143
+ - [Shadcn UI DropdownMenu](https://ui.shadcn.com/docs/components/dropdown-menu) - DropdownMenu 元件文件
1144
+ - [Firestore Data Model](https://firebase.google.com/docs/firestore/data-model) - Firestore 資料模型
1145
+ - [Express Rate Limiting](https://www.npmjs.com/package/express-rate-limit) - API 速率限制套件
1146
+ - [Node.js crypto.createHash()](https://nodejs.org/api/crypto.html#cryptocreatehashalgorithm-options) - MD5 hash 生成 API