switchroom 0.5.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 (718) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +447 -0
  3. package/bin/autoaccept.exp +81 -0
  4. package/bin/boot-self-test.sh +149 -0
  5. package/bin/bridge-watchdog.sh +967 -0
  6. package/bin/handoff-briefing.sh +206 -0
  7. package/bin/run-hook.sh +228 -0
  8. package/bin/switchroom.ts +4 -0
  9. package/bin/timezone-hook.sh +67 -0
  10. package/bin/user-profile-refresh-hook.sh +38 -0
  11. package/bin/workspace-dynamic-hook.sh +142 -0
  12. package/bin/workspace-stable-hook.sh +57 -0
  13. package/dist/cli/autoaccept-poll.js +118 -0
  14. package/dist/cli/switchroom.js +48557 -0
  15. package/package.json +95 -0
  16. package/profiles/_base/settings.json.hbs +15 -0
  17. package/profiles/_base/start.sh.hbs +383 -0
  18. package/profiles/_shared/telegram-style.md.hbs +140 -0
  19. package/profiles/coding/CLAUDE.md.hbs +57 -0
  20. package/profiles/coding/skills/architecture/SKILL.md +70 -0
  21. package/profiles/coding/skills/code-review/SKILL.md +58 -0
  22. package/profiles/coding/workspace/SOUL.md.hbs +25 -0
  23. package/profiles/default/CLAUDE.md +238 -0
  24. package/profiles/default/CLAUDE.md.hbs +113 -0
  25. package/profiles/default/workspace/CLAUDE.md.hbs +126 -0
  26. package/profiles/default/workspace/HEARTBEAT.md.hbs +40 -0
  27. package/profiles/default/workspace/IDENTITY.md.hbs +32 -0
  28. package/profiles/default/workspace/MEMORY.md.hbs +29 -0
  29. package/profiles/default/workspace/SOUL.md.hbs +61 -0
  30. package/profiles/default/workspace/TOOLS.md.hbs +29 -0
  31. package/profiles/default/workspace/USER.md.hbs +52 -0
  32. package/profiles/default/workspace/memory/.gitkeep +0 -0
  33. package/profiles/executive-assistant/CLAUDE.md.hbs +51 -0
  34. package/profiles/executive-assistant/skills/daily-briefing/SKILL.md +55 -0
  35. package/profiles/executive-assistant/skills/meeting-prep/SKILL.md +58 -0
  36. package/profiles/executive-assistant/workspace/SOUL.md.hbs +25 -0
  37. package/profiles/health-coach/CLAUDE.md.hbs +45 -0
  38. package/profiles/health-coach/skills/check-in/SKILL.md +41 -0
  39. package/profiles/health-coach/skills/weekly-review/SKILL.md +53 -0
  40. package/profiles/health-coach/workspace/SOUL.md.hbs +25 -0
  41. package/skills/buildkite-agent-infrastructure/SKILL.md +302 -0
  42. package/skills/buildkite-agent-infrastructure/agents/openai.yaml +6 -0
  43. package/skills/buildkite-agent-infrastructure/assets/buildkite-icon-large.png +0 -0
  44. package/skills/buildkite-agent-infrastructure/assets/buildkite-icon-small.png +0 -0
  45. package/skills/buildkite-agent-infrastructure/references/audit-logging.md +87 -0
  46. package/skills/buildkite-agent-infrastructure/references/graphql-mutations.md +690 -0
  47. package/skills/buildkite-agent-infrastructure/references/instance-shapes.md +38 -0
  48. package/skills/buildkite-agent-infrastructure/references/pipeline-templates.md +73 -0
  49. package/skills/buildkite-agent-infrastructure/references/self-hosted-agents.md +137 -0
  50. package/skills/buildkite-agent-infrastructure/references/sso-saml.md +92 -0
  51. package/skills/buildkite-agent-runtime/SKILL.md +476 -0
  52. package/skills/buildkite-agent-runtime/agents/openai.yaml +6 -0
  53. package/skills/buildkite-agent-runtime/assets/buildkite-icon-large.png +0 -0
  54. package/skills/buildkite-agent-runtime/assets/buildkite-icon-small.png +0 -0
  55. package/skills/buildkite-agent-runtime/references/flag-reference.md +417 -0
  56. package/skills/buildkite-agent-runtime/references/patterns-and-recipes.md +555 -0
  57. package/skills/buildkite-api/SKILL.md +285 -0
  58. package/skills/buildkite-api/agents/openai.yaml +6 -0
  59. package/skills/buildkite-api/assets/buildkite-icon-large.png +0 -0
  60. package/skills/buildkite-api/assets/buildkite-icon-small.png +0 -0
  61. package/skills/buildkite-api/references/graphql-reference.md +195 -0
  62. package/skills/buildkite-api/references/patterns.md +44 -0
  63. package/skills/buildkite-api/references/webhooks.md +161 -0
  64. package/skills/buildkite-cli/SKILL.md +379 -0
  65. package/skills/buildkite-cli/agents/openai.yaml +6 -0
  66. package/skills/buildkite-cli/assets/buildkite-icon-large.png +0 -0
  67. package/skills/buildkite-cli/assets/buildkite-icon-small.png +0 -0
  68. package/skills/buildkite-cli/references/command-reference.md +181 -0
  69. package/skills/buildkite-migration/SKILL.md +182 -0
  70. package/skills/buildkite-pipelines/SKILL.md +464 -0
  71. package/skills/buildkite-pipelines/agents/openai.yaml +6 -0
  72. package/skills/buildkite-pipelines/assets/buildkite-icon-large.png +0 -0
  73. package/skills/buildkite-pipelines/assets/buildkite-icon-small.png +0 -0
  74. package/skills/buildkite-pipelines/examples/basic-pipeline.yml +24 -0
  75. package/skills/buildkite-pipelines/examples/optimized-pipeline.yml +100 -0
  76. package/skills/buildkite-pipelines/references/advanced-patterns.md +286 -0
  77. package/skills/buildkite-pipelines/references/retry-and-error-codes.md +131 -0
  78. package/skills/buildkite-pipelines/references/step-types-reference.md +225 -0
  79. package/skills/buildkite-secure-delivery/SKILL.md +168 -0
  80. package/skills/buildkite-secure-delivery/agents/openai.yaml +6 -0
  81. package/skills/buildkite-secure-delivery/assets/buildkite-icon-large.png +0 -0
  82. package/skills/buildkite-secure-delivery/assets/buildkite-icon-small.png +0 -0
  83. package/skills/buildkite-secure-delivery/references/oidc-cloud-providers.md +83 -0
  84. package/skills/buildkite-secure-delivery/references/package-publishing.md +100 -0
  85. package/skills/buildkite-test-engine/SKILL.md +239 -0
  86. package/skills/buildkite-test-engine/agents/openai.yaml +6 -0
  87. package/skills/buildkite-test-engine/assets/buildkite-icon-large.png +0 -0
  88. package/skills/buildkite-test-engine/assets/buildkite-icon-small.png +0 -0
  89. package/skills/buildkite-test-engine/examples/bktec-splitting.yml +16 -0
  90. package/skills/buildkite-test-engine/examples/collector-pipeline.yml +11 -0
  91. package/skills/buildkite-test-engine/references/collectors.md +198 -0
  92. package/skills/buildkite-test-engine/references/splitting-examples.md +93 -0
  93. package/skills/docx/LICENSE.txt +30 -0
  94. package/skills/docx/SKILL.md +590 -0
  95. package/skills/docx/VENDORED.md +32 -0
  96. package/skills/docx/scripts/__init__.py +1 -0
  97. package/skills/docx/scripts/accept_changes.py +135 -0
  98. package/skills/docx/scripts/comment.py +318 -0
  99. package/skills/docx/scripts/office/helpers/__init__.py +0 -0
  100. package/skills/docx/scripts/office/helpers/merge_runs.py +199 -0
  101. package/skills/docx/scripts/office/helpers/simplify_redlines.py +197 -0
  102. package/skills/docx/scripts/office/pack.py +159 -0
  103. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  104. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  105. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  106. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  107. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  108. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  109. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  110. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  111. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  112. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  113. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  114. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  115. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  116. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  117. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  118. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  119. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  120. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  121. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  122. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  123. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  124. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  125. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  126. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  127. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  128. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  129. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  130. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  131. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  132. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  133. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  134. package/skills/docx/scripts/office/schemas/mce/mc.xsd +75 -0
  135. package/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  136. package/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  137. package/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  138. package/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  139. package/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  140. package/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  141. package/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  142. package/skills/docx/scripts/office/soffice.py +183 -0
  143. package/skills/docx/scripts/office/unpack.py +132 -0
  144. package/skills/docx/scripts/office/validate.py +111 -0
  145. package/skills/docx/scripts/office/validators/__init__.py +15 -0
  146. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-313.pyc +0 -0
  147. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-313.pyc +0 -0
  148. package/skills/docx/scripts/office/validators/base.py +847 -0
  149. package/skills/docx/scripts/office/validators/docx.py +446 -0
  150. package/skills/docx/scripts/office/validators/pptx.py +275 -0
  151. package/skills/docx/scripts/office/validators/redlining.py +247 -0
  152. package/skills/docx/scripts/templates/comments.xml +3 -0
  153. package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  154. package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  155. package/skills/docx/scripts/templates/commentsIds.xml +3 -0
  156. package/skills/docx/scripts/templates/people.xml +3 -0
  157. package/skills/file-bug/SKILL.md +129 -0
  158. package/skills/humanizer/LICENSE +21 -0
  159. package/skills/humanizer/SKILL.md +559 -0
  160. package/skills/humanizer/VENDORED.md +38 -0
  161. package/skills/humanizer-calibrate/SKILL.md +144 -0
  162. package/skills/mcp-builder/LICENSE.txt +202 -0
  163. package/skills/mcp-builder/SKILL.md +236 -0
  164. package/skills/mcp-builder/VENDORED.md +32 -0
  165. package/skills/mcp-builder/reference/evaluation.md +602 -0
  166. package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  167. package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  168. package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  169. package/skills/mcp-builder/scripts/connections.py +151 -0
  170. package/skills/mcp-builder/scripts/evaluation.py +373 -0
  171. package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  172. package/skills/mcp-builder/scripts/requirements.txt +2 -0
  173. package/skills/pdf/LICENSE.txt +30 -0
  174. package/skills/pdf/SKILL.md +314 -0
  175. package/skills/pdf/VENDORED.md +32 -0
  176. package/skills/pdf/forms.md +294 -0
  177. package/skills/pdf/reference.md +612 -0
  178. package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
  179. package/skills/pdf/scripts/check_fillable_fields.py +11 -0
  180. package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
  181. package/skills/pdf/scripts/create_validation_image.py +37 -0
  182. package/skills/pdf/scripts/extract_form_field_info.py +122 -0
  183. package/skills/pdf/scripts/extract_form_structure.py +115 -0
  184. package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
  185. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  186. package/skills/pptx/LICENSE.txt +30 -0
  187. package/skills/pptx/SKILL.md +232 -0
  188. package/skills/pptx/VENDORED.md +32 -0
  189. package/skills/pptx/editing.md +205 -0
  190. package/skills/pptx/pptxgenjs.md +420 -0
  191. package/skills/pptx/scripts/__init__.py +0 -0
  192. package/skills/pptx/scripts/add_slide.py +195 -0
  193. package/skills/pptx/scripts/clean.py +286 -0
  194. package/skills/pptx/scripts/office/helpers/__init__.py +0 -0
  195. package/skills/pptx/scripts/office/helpers/merge_runs.py +199 -0
  196. package/skills/pptx/scripts/office/helpers/simplify_redlines.py +197 -0
  197. package/skills/pptx/scripts/office/pack.py +159 -0
  198. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  199. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  200. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  201. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  202. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  203. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  204. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  205. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  206. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  207. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  208. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  209. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  210. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  211. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  212. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  213. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  214. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  215. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  216. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  217. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  218. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  219. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  220. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  221. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  222. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  223. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  224. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  225. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  226. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  227. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  228. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  229. package/skills/pptx/scripts/office/schemas/mce/mc.xsd +75 -0
  230. package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  231. package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  232. package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  233. package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  234. package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  235. package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  236. package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  237. package/skills/pptx/scripts/office/soffice.py +183 -0
  238. package/skills/pptx/scripts/office/unpack.py +132 -0
  239. package/skills/pptx/scripts/office/validate.py +111 -0
  240. package/skills/pptx/scripts/office/validators/__init__.py +15 -0
  241. package/skills/pptx/scripts/office/validators/base.py +847 -0
  242. package/skills/pptx/scripts/office/validators/docx.py +446 -0
  243. package/skills/pptx/scripts/office/validators/pptx.py +275 -0
  244. package/skills/pptx/scripts/office/validators/redlining.py +247 -0
  245. package/skills/pptx/scripts/thumbnail.py +289 -0
  246. package/skills/skill-creator/LICENSE.txt +202 -0
  247. package/skills/skill-creator/SKILL.md +485 -0
  248. package/skills/skill-creator/VENDORED.md +32 -0
  249. package/skills/skill-creator/agents/analyzer.md +274 -0
  250. package/skills/skill-creator/agents/comparator.md +202 -0
  251. package/skills/skill-creator/agents/grader.md +223 -0
  252. package/skills/skill-creator/assets/eval_review.html +146 -0
  253. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  254. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  255. package/skills/skill-creator/references/schemas.md +430 -0
  256. package/skills/skill-creator/scripts/__init__.py +0 -0
  257. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  258. package/skills/skill-creator/scripts/generate_report.py +326 -0
  259. package/skills/skill-creator/scripts/improve_description.py +247 -0
  260. package/skills/skill-creator/scripts/package_skill.py +136 -0
  261. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  262. package/skills/skill-creator/scripts/run_eval.py +310 -0
  263. package/skills/skill-creator/scripts/run_loop.py +328 -0
  264. package/skills/skill-creator/scripts/utils.py +47 -0
  265. package/skills/switchroom-architecture/SKILL.md +60 -0
  266. package/skills/switchroom-architecture/cascade.md +112 -0
  267. package/skills/switchroom-architecture/sub-agents.md +87 -0
  268. package/skills/switchroom-architecture/telegram.md +94 -0
  269. package/skills/switchroom-cli/SKILL.md +274 -0
  270. package/skills/switchroom-health/SKILL.md +101 -0
  271. package/skills/switchroom-install/SKILL.md +116 -0
  272. package/skills/switchroom-manage/SKILL.md +90 -0
  273. package/skills/switchroom-status/SKILL.md +69 -0
  274. package/skills/switchroom-status/scripts/status.sh +69 -0
  275. package/skills/telegram-test-harness/SKILL.md +191 -0
  276. package/skills/token-helpers/SKILL.md +73 -0
  277. package/skills/token-helpers/scripts/google-cal-token.sh +62 -0
  278. package/skills/token-helpers/scripts/ms-graph-token.sh +70 -0
  279. package/skills/webapp-testing/LICENSE.txt +202 -0
  280. package/skills/webapp-testing/SKILL.md +96 -0
  281. package/skills/webapp-testing/VENDORED.md +32 -0
  282. package/skills/webapp-testing/examples/console_logging.py +35 -0
  283. package/skills/webapp-testing/examples/element_discovery.py +40 -0
  284. package/skills/webapp-testing/examples/static_html_automation.py +33 -0
  285. package/skills/webapp-testing/scripts/with_server.py +106 -0
  286. package/skills/xlsx/LICENSE.txt +30 -0
  287. package/skills/xlsx/SKILL.md +292 -0
  288. package/skills/xlsx/VENDORED.md +32 -0
  289. package/skills/xlsx/scripts/office/helpers/__init__.py +0 -0
  290. package/skills/xlsx/scripts/office/helpers/merge_runs.py +199 -0
  291. package/skills/xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
  292. package/skills/xlsx/scripts/office/pack.py +159 -0
  293. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  294. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  295. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  296. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  297. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  298. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  299. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  300. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  301. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  302. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  303. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  304. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  305. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  306. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  307. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  308. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  309. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  310. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  311. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  312. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  313. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  314. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  315. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  316. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  317. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  318. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  319. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  320. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  321. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  322. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  323. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  324. package/skills/xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
  325. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  326. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  327. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  328. package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  329. package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  330. package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  331. package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  332. package/skills/xlsx/scripts/office/soffice.py +183 -0
  333. package/skills/xlsx/scripts/office/unpack.py +132 -0
  334. package/skills/xlsx/scripts/office/validate.py +111 -0
  335. package/skills/xlsx/scripts/office/validators/__init__.py +15 -0
  336. package/skills/xlsx/scripts/office/validators/base.py +847 -0
  337. package/skills/xlsx/scripts/office/validators/docx.py +446 -0
  338. package/skills/xlsx/scripts/office/validators/pptx.py +275 -0
  339. package/skills/xlsx/scripts/office/validators/redlining.py +247 -0
  340. package/skills/xlsx/scripts/recalc.py +184 -0
  341. package/telegram-plugin/.claude-plugin/plugin.json +20 -0
  342. package/telegram-plugin/.mcp.json +14 -0
  343. package/telegram-plugin/LICENSE +21 -0
  344. package/telegram-plugin/README.md +352 -0
  345. package/telegram-plugin/active-pins-sweep.ts +204 -0
  346. package/telegram-plugin/active-pins.ts +146 -0
  347. package/telegram-plugin/active-reactions-sweep.ts +79 -0
  348. package/telegram-plugin/active-reactions.ts +134 -0
  349. package/telegram-plugin/admin-commands/dispatch.test.ts +149 -0
  350. package/telegram-plugin/admin-commands/index.ts +106 -0
  351. package/telegram-plugin/answer-stream.ts +565 -0
  352. package/telegram-plugin/ask-user.ts +179 -0
  353. package/telegram-plugin/attachment-path.ts +80 -0
  354. package/telegram-plugin/auth-code-redact.ts +83 -0
  355. package/telegram-plugin/auth-dashboard.ts +1104 -0
  356. package/telegram-plugin/auth-slot-parser.ts +497 -0
  357. package/telegram-plugin/auto-fallback-dispatcher.ts +68 -0
  358. package/telegram-plugin/auto-fallback.ts +348 -0
  359. package/telegram-plugin/bridge/bridge.ts +687 -0
  360. package/telegram-plugin/bridge/ipc-client.ts +326 -0
  361. package/telegram-plugin/bun.lock +218 -0
  362. package/telegram-plugin/card-format.ts +62 -0
  363. package/telegram-plugin/channel-envelope-safety.test.ts +56 -0
  364. package/telegram-plugin/channel-envelope-safety.ts +56 -0
  365. package/telegram-plugin/chat-lock.ts +65 -0
  366. package/telegram-plugin/context-exhaustion.ts +38 -0
  367. package/telegram-plugin/credits-watch.ts +220 -0
  368. package/telegram-plugin/dist/bridge/bridge.js +24758 -0
  369. package/telegram-plugin/dist/foreman/foreman.js +30723 -0
  370. package/telegram-plugin/dist/gateway/gateway.js +46497 -0
  371. package/telegram-plugin/dist/server.js +24551 -0
  372. package/telegram-plugin/dm-command-gate.ts +56 -0
  373. package/telegram-plugin/docs/gateway-server-split.md +133 -0
  374. package/telegram-plugin/docs/multi-agent-card-design.md +847 -0
  375. package/telegram-plugin/docs/pinned-progress-card-reliability.md +144 -0
  376. package/telegram-plugin/docs/stream-json-daemon-mode.md +477 -0
  377. package/telegram-plugin/docs/waiting-ux-spec.md +233 -0
  378. package/telegram-plugin/draft-stream.ts +442 -0
  379. package/telegram-plugin/draft-transport.ts +72 -0
  380. package/telegram-plugin/first-paint.ts +246 -0
  381. package/telegram-plugin/fleet-state.ts +246 -0
  382. package/telegram-plugin/foreman/foreman-create-flow.ts +202 -0
  383. package/telegram-plugin/foreman/foreman-handlers.ts +493 -0
  384. package/telegram-plugin/foreman/foreman.ts +1130 -0
  385. package/telegram-plugin/foreman/setup-flow.ts +345 -0
  386. package/telegram-plugin/foreman/setup-state.ts +239 -0
  387. package/telegram-plugin/foreman/state.ts +203 -0
  388. package/telegram-plugin/format.ts +685 -0
  389. package/telegram-plugin/gateway/access-validator.test.ts +95 -0
  390. package/telegram-plugin/gateway/access-validator.ts +37 -0
  391. package/telegram-plugin/gateway/boot-card.ts +582 -0
  392. package/telegram-plugin/gateway/boot-probes.ts +863 -0
  393. package/telegram-plugin/gateway/boot-reason.ts +51 -0
  394. package/telegram-plugin/gateway/boot-sweep-filter.test.ts +54 -0
  395. package/telegram-plugin/gateway/boot-sweep-filter.ts +32 -0
  396. package/telegram-plugin/gateway/clean-shutdown-marker.ts +183 -0
  397. package/telegram-plugin/gateway/disconnect-flush.ts +109 -0
  398. package/telegram-plugin/gateway/gateway.ts +10202 -0
  399. package/telegram-plugin/gateway/inbound-coalesce.ts +147 -0
  400. package/telegram-plugin/gateway/inject-handler.test.ts +221 -0
  401. package/telegram-plugin/gateway/inject-handler.ts +190 -0
  402. package/telegram-plugin/gateway/ipc-protocol.ts +151 -0
  403. package/telegram-plugin/gateway/ipc-server.ts +494 -0
  404. package/telegram-plugin/gateway/pid-file.ts +103 -0
  405. package/telegram-plugin/gateway/poll-health.ts +156 -0
  406. package/telegram-plugin/gateway/preamble-suppressor.ts +154 -0
  407. package/telegram-plugin/gateway/quota-cache.ts +125 -0
  408. package/telegram-plugin/gateway/resolve-calling-subagent.ts +78 -0
  409. package/telegram-plugin/gateway/restart-watchdog.ts +200 -0
  410. package/telegram-plugin/gateway/session-marker.ts +83 -0
  411. package/telegram-plugin/gateway/shutdown-drain.ts +162 -0
  412. package/telegram-plugin/gateway/startup-mutex.ts +285 -0
  413. package/telegram-plugin/gateway/startup-network-retry.ts +142 -0
  414. package/telegram-plugin/gateway/turn-active-marker.ts +176 -0
  415. package/telegram-plugin/gateway/unhandled-rejection-policy.ts +78 -0
  416. package/telegram-plugin/handoff-continuity.ts +200 -0
  417. package/telegram-plugin/history.ts +468 -0
  418. package/telegram-plugin/hooks/hooks.json +58 -0
  419. package/telegram-plugin/hooks/secret-guard-pretool.mjs +208 -0
  420. package/telegram-plugin/hooks/secret-scrub-stop.mjs +98 -0
  421. package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +111 -0
  422. package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +296 -0
  423. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +261 -0
  424. package/telegram-plugin/html-sanitize.ts +244 -0
  425. package/telegram-plugin/idle-footer.ts +65 -0
  426. package/telegram-plugin/inline-keyboard-callbacks.ts +166 -0
  427. package/telegram-plugin/interrupt-marker.ts +66 -0
  428. package/telegram-plugin/issues-card.ts +371 -0
  429. package/telegram-plugin/issues-watcher.ts +125 -0
  430. package/telegram-plugin/model-unavailable.ts +325 -0
  431. package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  432. package/telegram-plugin/operator-events-history.ts +94 -0
  433. package/telegram-plugin/operator-events.fixtures.json +161 -0
  434. package/telegram-plugin/operator-events.ts +421 -0
  435. package/telegram-plugin/package.json +55 -0
  436. package/telegram-plugin/permission-rule.ts +133 -0
  437. package/telegram-plugin/permission-title.ts +117 -0
  438. package/telegram-plugin/pin-event-log.ts +76 -0
  439. package/telegram-plugin/plugin-logger.ts +136 -0
  440. package/telegram-plugin/progress-card-driver.ts +2697 -0
  441. package/telegram-plugin/progress-card-pin-manager.ts +589 -0
  442. package/telegram-plugin/progress-card-pin-watchdog.ts +98 -0
  443. package/telegram-plugin/progress-card.ts +1409 -0
  444. package/telegram-plugin/pty-partial-handler.ts +247 -0
  445. package/telegram-plugin/pty-tail.ts +730 -0
  446. package/telegram-plugin/quota-check.ts +474 -0
  447. package/telegram-plugin/recent-outbound-dedup.ts +169 -0
  448. package/telegram-plugin/registry/api-registry.test.ts +201 -0
  449. package/telegram-plugin/registry/subagents-bugs.test.ts +454 -0
  450. package/telegram-plugin/registry/subagents-schema.ts +509 -0
  451. package/telegram-plugin/registry/subagents.test.ts +476 -0
  452. package/telegram-plugin/registry/turns-schema.test.ts +101 -0
  453. package/telegram-plugin/registry/turns-schema.ts +417 -0
  454. package/telegram-plugin/retry-api-call.ts +172 -0
  455. package/telegram-plugin/scripts/build.mjs +78 -0
  456. package/telegram-plugin/secret-detect/audit.ts +66 -0
  457. package/telegram-plugin/secret-detect/chunker.ts +37 -0
  458. package/telegram-plugin/secret-detect/entropy.ts +20 -0
  459. package/telegram-plugin/secret-detect/gitleaks-loader.ts +74 -0
  460. package/telegram-plugin/secret-detect/gitleaks.toml +27 -0
  461. package/telegram-plugin/secret-detect/index.ts +218 -0
  462. package/telegram-plugin/secret-detect/kv-scanner.ts +60 -0
  463. package/telegram-plugin/secret-detect/mask.ts +13 -0
  464. package/telegram-plugin/secret-detect/patterns.ts +115 -0
  465. package/telegram-plugin/secret-detect/pipeline.ts +144 -0
  466. package/telegram-plugin/secret-detect/rewrite.ts +26 -0
  467. package/telegram-plugin/secret-detect/secretlint-source.ts +95 -0
  468. package/telegram-plugin/secret-detect/slug.ts +44 -0
  469. package/telegram-plugin/secret-detect/staging.ts +85 -0
  470. package/telegram-plugin/secret-detect/suppressor.ts +34 -0
  471. package/telegram-plugin/secret-detect/url-redact.ts +60 -0
  472. package/telegram-plugin/secret-detect/vault-write.ts +56 -0
  473. package/telegram-plugin/server.js +41795 -0
  474. package/telegram-plugin/server.ts +171 -0
  475. package/telegram-plugin/session-tail.ts +884 -0
  476. package/telegram-plugin/shared/bot-runtime.ts +324 -0
  477. package/telegram-plugin/silent-reply.ts +58 -0
  478. package/telegram-plugin/slot-banner-driver.ts +147 -0
  479. package/telegram-plugin/slot-banner.ts +86 -0
  480. package/telegram-plugin/start.js +26 -0
  481. package/telegram-plugin/startup-reset.ts +45 -0
  482. package/telegram-plugin/status-reactions.ts +332 -0
  483. package/telegram-plugin/steering.ts +155 -0
  484. package/telegram-plugin/sticker-aliases.ts +249 -0
  485. package/telegram-plugin/stream-controller.ts +311 -0
  486. package/telegram-plugin/stream-reply-handler.ts +664 -0
  487. package/telegram-plugin/streaming-metrics.ts +134 -0
  488. package/telegram-plugin/streaming-report.ts +204 -0
  489. package/telegram-plugin/subagent-watcher.ts +880 -0
  490. package/telegram-plugin/telegram-button-constraints.ts +191 -0
  491. package/telegram-plugin/telegraph.ts +381 -0
  492. package/telegram-plugin/tests/HARNESS.md +340 -0
  493. package/telegram-plugin/tests/_progress-card-harness.ts +105 -0
  494. package/telegram-plugin/tests/active-pins-boot-reaper.test.ts +211 -0
  495. package/telegram-plugin/tests/active-pins-sweep.test.ts +309 -0
  496. package/telegram-plugin/tests/active-pins.test.ts +187 -0
  497. package/telegram-plugin/tests/active-reactions-sweep.test.ts +116 -0
  498. package/telegram-plugin/tests/active-reactions.test.ts +198 -0
  499. package/telegram-plugin/tests/answer-stream-dedup.test.ts +352 -0
  500. package/telegram-plugin/tests/answer-stream-silent-markers.test.ts +236 -0
  501. package/telegram-plugin/tests/answer-stream.test.ts +878 -0
  502. package/telegram-plugin/tests/ask-user.test.ts +203 -0
  503. package/telegram-plugin/tests/attachment-path.test.ts +199 -0
  504. package/telegram-plugin/tests/auth-account-identity-surface.test.ts +118 -0
  505. package/telegram-plugin/tests/auth-code-auto-capture.test.ts +144 -0
  506. package/telegram-plugin/tests/auth-code-redact.test.ts +248 -0
  507. package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +260 -0
  508. package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +140 -0
  509. package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +559 -0
  510. package/telegram-plugin/tests/auth-dashboard.test.ts +1045 -0
  511. package/telegram-plugin/tests/auth-login-url-button.test.ts +122 -0
  512. package/telegram-plugin/tests/auth-slot-commands.test.ts +640 -0
  513. package/telegram-plugin/tests/auto-fallback-dispatcher.e2e.test.ts +183 -0
  514. package/telegram-plugin/tests/auto-fallback.test.ts +381 -0
  515. package/telegram-plugin/tests/boot-card-account-quota.test.ts +137 -0
  516. package/telegram-plugin/tests/boot-card-dedupe.test.ts +154 -0
  517. package/telegram-plugin/tests/boot-card-probe-target.test.ts +194 -0
  518. package/telegram-plugin/tests/boot-card-reason.test.ts +103 -0
  519. package/telegram-plugin/tests/boot-card-render.test.ts +219 -0
  520. package/telegram-plugin/tests/boot-probes.test.ts +451 -0
  521. package/telegram-plugin/tests/bot-api.harness.ts +116 -0
  522. package/telegram-plugin/tests/bot-runtime.test.ts +190 -0
  523. package/telegram-plugin/tests/bridge-anonymous-refuse.test.ts +60 -0
  524. package/telegram-plugin/tests/context-exhaustion.test.ts +114 -0
  525. package/telegram-plugin/tests/credits-watch.test.ts +221 -0
  526. package/telegram-plugin/tests/dm-command-gate.test.ts +176 -0
  527. package/telegram-plugin/tests/draft-stream.test.ts +752 -0
  528. package/telegram-plugin/tests/draft-transport.test.ts +141 -0
  529. package/telegram-plugin/tests/e2e.test.ts +436 -0
  530. package/telegram-plugin/tests/fake-bot-api.test.ts +213 -0
  531. package/telegram-plugin/tests/fake-bot-api.ts +617 -0
  532. package/telegram-plugin/tests/false-restart-banner.test.ts +253 -0
  533. package/telegram-plugin/tests/first-paint.test.ts +257 -0
  534. package/telegram-plugin/tests/fixtures/pty-tail-tmux-fragment.bin +6 -0
  535. package/telegram-plugin/tests/fixtures/service-log-current-claude-code.bin +3624 -0
  536. package/telegram-plugin/tests/fleet-state-watcher.test.ts +101 -0
  537. package/telegram-plugin/tests/fleet-state.test.ts +185 -0
  538. package/telegram-plugin/tests/foreman-create-flow.test.ts +359 -0
  539. package/telegram-plugin/tests/foreman-handlers.test.ts +347 -0
  540. package/telegram-plugin/tests/foreman-state.test.ts +164 -0
  541. package/telegram-plugin/tests/foreman-write-ops.test.ts +214 -0
  542. package/telegram-plugin/tests/gateway-409-retry-banner.test.ts +173 -0
  543. package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +72 -0
  544. package/telegram-plugin/tests/gateway-bridge.test.ts +811 -0
  545. package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +414 -0
  546. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +144 -0
  547. package/telegram-plugin/tests/gateway-message-validator.test.ts +133 -0
  548. package/telegram-plugin/tests/gateway-no-reply-single-emit.test.ts +103 -0
  549. package/telegram-plugin/tests/gateway-secret-detect.test.ts +127 -0
  550. package/telegram-plugin/tests/gateway-startup-mutex.test.ts +284 -0
  551. package/telegram-plugin/tests/gateway-startup-network-retry.test.ts +185 -0
  552. package/telegram-plugin/tests/gateway-startup-reset.test.ts +72 -0
  553. package/telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts +125 -0
  554. package/telegram-plugin/tests/handoff-continuity.test.ts +249 -0
  555. package/telegram-plugin/tests/harness-ordering-invariants.test.ts +243 -0
  556. package/telegram-plugin/tests/harness-parse-mode-validation.test.ts +114 -0
  557. package/telegram-plugin/tests/history.test.ts +364 -0
  558. package/telegram-plugin/tests/html-balanced.ts +63 -0
  559. package/telegram-plugin/tests/html-sanitize.test.ts +146 -0
  560. package/telegram-plugin/tests/idle-footer-wiring.test.ts +88 -0
  561. package/telegram-plugin/tests/idle-footer.test.ts +66 -0
  562. package/telegram-plugin/tests/inbound-coalesce.test.ts +127 -0
  563. package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +150 -0
  564. package/telegram-plugin/tests/interrupt-marker.test.ts +126 -0
  565. package/telegram-plugin/tests/ipc-protocol.test.ts +218 -0
  566. package/telegram-plugin/tests/ipc-server-anonymous-refuse.test.ts +82 -0
  567. package/telegram-plugin/tests/ipc-server-client.test.ts +323 -0
  568. package/telegram-plugin/tests/ipc-server-race.test.ts +183 -0
  569. package/telegram-plugin/tests/ipc-server-validate-operator.test.ts +91 -0
  570. package/telegram-plugin/tests/ipc-server-validate-pty-partial.test.ts +64 -0
  571. package/telegram-plugin/tests/ipc-server-validate-update-placeholder.test.ts +77 -0
  572. package/telegram-plugin/tests/ipc-validator.test.ts +274 -0
  573. package/telegram-plugin/tests/issues-card.test.ts +495 -0
  574. package/telegram-plugin/tests/issues-watcher.test.ts +165 -0
  575. package/telegram-plugin/tests/model-unavailable.test.ts +303 -0
  576. package/telegram-plugin/tests/multi-turn-continuity.test.ts +159 -0
  577. package/telegram-plugin/tests/operator-events-history.test.ts +125 -0
  578. package/telegram-plugin/tests/operator-events-session-tail.test.ts +192 -0
  579. package/telegram-plugin/tests/operator-events.test.ts +331 -0
  580. package/telegram-plugin/tests/outbound-ordering.test.ts +293 -0
  581. package/telegram-plugin/tests/parse-mode-rotation.test.ts +164 -0
  582. package/telegram-plugin/tests/permission-rule.test.ts +121 -0
  583. package/telegram-plugin/tests/permission-title.test.ts +106 -0
  584. package/telegram-plugin/tests/pin-event-log.test.ts +124 -0
  585. package/telegram-plugin/tests/plugin-logger.test.ts +97 -0
  586. package/telegram-plugin/tests/poll-health.test.ts +86 -0
  587. package/telegram-plugin/tests/preamble-suppressor.test.ts +285 -0
  588. package/telegram-plugin/tests/progress-card-api-failure-during-deferred.test.ts +73 -0
  589. package/telegram-plugin/tests/progress-card-close-paths-converge.test.ts +272 -0
  590. package/telegram-plugin/tests/progress-card-cross-turn.test.ts +258 -0
  591. package/telegram-plugin/tests/progress-card-dispose-preservepending.test.ts +81 -0
  592. package/telegram-plugin/tests/progress-card-draft-flag.test.ts +80 -0
  593. package/telegram-plugin/tests/progress-card-driver-eviction.test.ts +215 -0
  594. package/telegram-plugin/tests/progress-card-driver-fleet-shadow.test.ts +123 -0
  595. package/telegram-plugin/tests/progress-card-driver-force-complete-parent-done.test.ts +76 -0
  596. package/telegram-plugin/tests/progress-card-edit-timestamps-budget.test.ts +62 -0
  597. package/telegram-plugin/tests/progress-card-memory-bounds.test.ts +84 -0
  598. package/telegram-plugin/tests/progress-card-pin-failure-paths.test.ts +139 -0
  599. package/telegram-plugin/tests/progress-card-pin-manager.test.ts +773 -0
  600. package/telegram-plugin/tests/progress-card-pin-race-fast-turn.test.ts +66 -0
  601. package/telegram-plugin/tests/progress-card-pin-sidecar-partial-write.test.ts +64 -0
  602. package/telegram-plugin/tests/progress-card-pin-watchdog.test.ts +190 -0
  603. package/telegram-plugin/tests/progress-card-sigterm-pin-flush.test.ts +146 -0
  604. package/telegram-plugin/tests/progress-update.test.ts +236 -0
  605. package/telegram-plugin/tests/protocol-fixtures.test.ts +59 -0
  606. package/telegram-plugin/tests/protocol-fixtures.ts +198 -0
  607. package/telegram-plugin/tests/pty-partial-handler.test.ts +326 -0
  608. package/telegram-plugin/tests/pty-tail-real-fixture.test.ts +114 -0
  609. package/telegram-plugin/tests/pty-tail-tmux-fragment.test.ts +71 -0
  610. package/telegram-plugin/tests/pty-tail.test.ts +525 -0
  611. package/telegram-plugin/tests/quota-cache.test.ts +187 -0
  612. package/telegram-plugin/tests/quota-check.test.ts +622 -0
  613. package/telegram-plugin/tests/races.test.ts +842 -0
  614. package/telegram-plugin/tests/real-gateway-f1-ladder-integrity.test.ts +123 -0
  615. package/telegram-plugin/tests/real-gateway-f2-instant-draft.test.ts +82 -0
  616. package/telegram-plugin/tests/real-gateway-f3-late-card.test.ts +114 -0
  617. package/telegram-plugin/tests/real-gateway-harness.ts +699 -0
  618. package/telegram-plugin/tests/real-gateway-i6-turn-flush-replay-dedup.test.ts +313 -0
  619. package/telegram-plugin/tests/real-gateway-ipc-lifecycle.test.ts +299 -0
  620. package/telegram-plugin/tests/real-gateway-spec.test.ts +487 -0
  621. package/telegram-plugin/tests/real-gateway.smoke.test.ts +101 -0
  622. package/telegram-plugin/tests/recent-outbound-dedup.test.ts +192 -0
  623. package/telegram-plugin/tests/registry-turns.test.ts +432 -0
  624. package/telegram-plugin/tests/reply-terminal-reaction.test.ts +149 -0
  625. package/telegram-plugin/tests/resolve-calling-subagent.test.ts +269 -0
  626. package/telegram-plugin/tests/restart-watchdog.test.ts +224 -0
  627. package/telegram-plugin/tests/retry-api-call.test.ts +287 -0
  628. package/telegram-plugin/tests/secret-detect-audit.test.ts +58 -0
  629. package/telegram-plugin/tests/secret-detect-fail-closed.test.ts +83 -0
  630. package/telegram-plugin/tests/secret-detect-gitleaks.test.ts +32 -0
  631. package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +308 -0
  632. package/telegram-plugin/tests/secret-detect-pipeline.test.ts +123 -0
  633. package/telegram-plugin/tests/secret-detect-secretlint.test.ts +101 -0
  634. package/telegram-plugin/tests/secret-detect-staging.test.ts +45 -0
  635. package/telegram-plugin/tests/secret-detect-suppressor-no-silent-allow.test.ts +67 -0
  636. package/telegram-plugin/tests/secret-detect.test.ts +223 -0
  637. package/telegram-plugin/tests/secret-guard-pretool.test.ts +194 -0
  638. package/telegram-plugin/tests/send-typing-action-validation.test.ts +61 -0
  639. package/telegram-plugin/tests/session-tail-capped.test.ts +285 -0
  640. package/telegram-plugin/tests/session-tail.test.ts +524 -0
  641. package/telegram-plugin/tests/setup-flow.test.ts +510 -0
  642. package/telegram-plugin/tests/setup-state.test.ts +146 -0
  643. package/telegram-plugin/tests/silent-reply-guard.test.ts +122 -0
  644. package/telegram-plugin/tests/slot-banner-driver.e2e.test.ts +350 -0
  645. package/telegram-plugin/tests/slot-banner.test.ts +74 -0
  646. package/telegram-plugin/tests/snapshot-serializer.ts +79 -0
  647. package/telegram-plugin/tests/spawn-detached-cgroup-escape.test.ts +51 -0
  648. package/telegram-plugin/tests/status-accent.test.ts +186 -0
  649. package/telegram-plugin/tests/status-reactions-allowed-filter.test.ts +132 -0
  650. package/telegram-plugin/tests/status-reactions.test.ts +314 -0
  651. package/telegram-plugin/tests/steering.test.ts +282 -0
  652. package/telegram-plugin/tests/sticker-aliases.test.ts +232 -0
  653. package/telegram-plugin/tests/stream-controller-html-fallback.test.ts +127 -0
  654. package/telegram-plugin/tests/stream-controller.test.ts +262 -0
  655. package/telegram-plugin/tests/stream-reply-error-paths.test.ts +208 -0
  656. package/telegram-plugin/tests/stream-reply-handler.test.ts +1292 -0
  657. package/telegram-plugin/tests/streaming-e2e.test.ts +389 -0
  658. package/telegram-plugin/tests/streaming-metrics.test.ts +201 -0
  659. package/telegram-plugin/tests/streaming-orchestration.test.ts +756 -0
  660. package/telegram-plugin/tests/subagent-registry-bugs.test.ts +725 -0
  661. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +213 -0
  662. package/telegram-plugin/tests/subagent-watcher-parent-marker.test.ts +274 -0
  663. package/telegram-plugin/tests/subagent-watcher-stall-notification.test.ts +243 -0
  664. package/telegram-plugin/tests/subagent-watcher.test.ts +877 -0
  665. package/telegram-plugin/tests/subagents-schema-init-order.test.ts +109 -0
  666. package/telegram-plugin/tests/sync-chat-running-subagents.test.ts +116 -0
  667. package/telegram-plugin/tests/telegram-button-constraints.test.ts +194 -0
  668. package/telegram-plugin/tests/telegram-format.test.ts +1093 -0
  669. package/telegram-plugin/tests/telegraph.test.ts +246 -0
  670. package/telegram-plugin/tests/tool-labels.test.ts +383 -0
  671. package/telegram-plugin/tests/turn-active-marker.test.ts +195 -0
  672. package/telegram-plugin/tests/turn-end-regressions.test.ts +489 -0
  673. package/telegram-plugin/tests/turn-flush-card-takeover.test.ts +218 -0
  674. package/telegram-plugin/tests/turn-flush-dedup-controller.test.ts +144 -0
  675. package/telegram-plugin/tests/turn-flush-prose-recovery.test.ts +78 -0
  676. package/telegram-plugin/tests/turn-flush-safety.test.ts +189 -0
  677. package/telegram-plugin/tests/turn-signal-tracker.test.ts +107 -0
  678. package/telegram-plugin/tests/turns-writer.test.ts +323 -0
  679. package/telegram-plugin/tests/two-zone-bg-carry-full-lifecycle.test.ts +131 -0
  680. package/telegram-plugin/tests/two-zone-bg-detection.test.ts +120 -0
  681. package/telegram-plugin/tests/two-zone-bg-done-when-all-terminal.test.ts +114 -0
  682. package/telegram-plugin/tests/two-zone-bg-early-turn-end.test.ts +87 -0
  683. package/telegram-plugin/tests/two-zone-bg-survives-next-turn.test.ts +211 -0
  684. package/telegram-plugin/tests/two-zone-card-cap.test.ts +62 -0
  685. package/telegram-plugin/tests/two-zone-card-fleet-row.test.ts +101 -0
  686. package/telegram-plugin/tests/two-zone-card-header-phases.test.ts +68 -0
  687. package/telegram-plugin/tests/two-zone-card-html-balance.test.ts +110 -0
  688. package/telegram-plugin/tests/two-zone-card-lifecycle.test.ts +128 -0
  689. package/telegram-plugin/tests/two-zone-card-sanitise.test.ts +58 -0
  690. package/telegram-plugin/tests/two-zone-card-snapshot.test.ts +133 -0
  691. package/telegram-plugin/tests/two-zone-concurrent-turns-isolation.test.ts +155 -0
  692. package/telegram-plugin/tests/two-zone-phasefor-precedence.test.ts +117 -0
  693. package/telegram-plugin/tests/two-zone-snapshot-extras.test.ts +143 -0
  694. package/telegram-plugin/tests/two-zone-stuck-edit-throttle.test.ts +149 -0
  695. package/telegram-plugin/tests/two-zone-stuck-header-escalation.test.ts +101 -0
  696. package/telegram-plugin/tests/two-zone-stuck-per-member.test.ts +114 -0
  697. package/telegram-plugin/tests/two-zone-stuck-recovery.test.ts +105 -0
  698. package/telegram-plugin/tests/typing-wrap.test.ts +141 -0
  699. package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +147 -0
  700. package/telegram-plugin/tests/update-factory-edited-and-reactions.test.ts +108 -0
  701. package/telegram-plugin/tests/update-factory.ts +305 -0
  702. package/telegram-plugin/tests/vault-grant-wizard.test.ts +84 -0
  703. package/telegram-plugin/tests/vault-grants-revoke.test.ts +265 -0
  704. package/telegram-plugin/tests/vault-subcommands.test.ts +234 -0
  705. package/telegram-plugin/tests/voice-transcribe.test.ts +196 -0
  706. package/telegram-plugin/tests/waiting-ux-harness.ts +381 -0
  707. package/telegram-plugin/tests/waiting-ux.e2e.test.ts +233 -0
  708. package/telegram-plugin/tests/welcome-text.test.ts +407 -0
  709. package/telegram-plugin/tool-error-filter.ts +89 -0
  710. package/telegram-plugin/tool-labels.ts +330 -0
  711. package/telegram-plugin/tool-names.ts +53 -0
  712. package/telegram-plugin/turn-flush-prose-recovery.ts +40 -0
  713. package/telegram-plugin/turn-flush-safety.ts +109 -0
  714. package/telegram-plugin/turn-signal-tracker.ts +79 -0
  715. package/telegram-plugin/two-zone-card.ts +249 -0
  716. package/telegram-plugin/typing-wrap.ts +92 -0
  717. package/telegram-plugin/voice-transcribe.ts +166 -0
  718. package/telegram-plugin/welcome-text.ts +359 -0
@@ -0,0 +1,474 @@
1
+ /**
2
+ * Pro/Max plan quota check — hits /v1/messages with the Claude CLI's OAuth
3
+ * auth + header shape and reads the rate-limit utilization values from the
4
+ * response headers. This is the same mechanism the TUI's /usage panel uses.
5
+ *
6
+ * Why this module exists: before discovering the header surface, Switchroom
7
+ * only had ccusage-based dollar-cost tracking (what you spent), not what
8
+ * the Pro/Max plan's 5-hour and 7-day rolling windows actually show. Those
9
+ * utilization values never appear in request/response bodies, only in
10
+ * headers, and only when the request is authenticated with a subscription
11
+ * OAuth token and carries the CLI's exact user-agent + beta headers.
12
+ *
13
+ * Returning `{ ok: false, reason }` instead of throwing lets callers
14
+ * (greeting hook, /usage command) render a graceful fallback row without
15
+ * having to catch.
16
+ */
17
+
18
+ import { readFileSync, existsSync } from "fs";
19
+ import { join } from "path";
20
+ import {
21
+ readAccountQuota,
22
+ snapshotFromQuotaUtilization,
23
+ writeAccountQuota,
24
+ } from "../src/auth/account-quota-store.js";
25
+
26
+ /**
27
+ * OAuth beta flag — proves the request is coming from a subscription client.
28
+ * Plain bearer OAuth tokens without this header are rejected with
29
+ * "OAuth authentication is currently not supported".
30
+ */
31
+ const OAUTH_BETA = "oauth-2025-04-20";
32
+
33
+ /**
34
+ * User-agent the CLI sends. Kept in sync with observed traffic;
35
+ * the server is lenient on the version suffix but strict on the
36
+ * overall shape ("claude-cli/X.Y.Z (external, cli)").
37
+ */
38
+ const DEFAULT_USER_AGENT = "claude-cli/1.0.0 (external, cli)";
39
+
40
+ /**
41
+ * Default model for the probe. Picked to minimize spend — one input token,
42
+ * max_tokens=1, a Haiku model. The response body is discarded; we only
43
+ * care about the headers.
44
+ */
45
+ const DEFAULT_PROBE_MODEL = "claude-haiku-4-5-20251001";
46
+
47
+ export type QuotaUtilization = {
48
+ fiveHourUtilizationPct: number;
49
+ sevenDayUtilizationPct: number;
50
+ fiveHourResetAt: Date | null;
51
+ sevenDayResetAt: Date | null;
52
+ representativeClaim: string | null;
53
+ overageStatus: string | null;
54
+ overageDisabledReason: string | null;
55
+ };
56
+
57
+ export type QuotaResult =
58
+ | { ok: true; data: QuotaUtilization }
59
+ | { ok: false; reason: string };
60
+
61
+ export type FetchQuotaOptions = {
62
+ /**
63
+ * Path to the agent's Claude config dir (contains `.oauth-token`).
64
+ * Mutually exclusive with `accessToken`. One of the two must be set.
65
+ */
66
+ claudeConfigDir?: string;
67
+ /**
68
+ * OAuth access token to probe with directly. Use this from the
69
+ * account-level path (`~/.switchroom/accounts/<label>/credentials.json`)
70
+ * where the credentials live in the new account model rather than
71
+ * a legacy `.oauth-token` file. Mutually exclusive with
72
+ * `claudeConfigDir`.
73
+ */
74
+ accessToken?: string;
75
+ /** Override probe model. Defaults to haiku-4-5. */
76
+ model?: string;
77
+ /** Abort after this many ms. Defaults to 10s. */
78
+ timeoutMs?: number;
79
+ /** Override fetch for tests. */
80
+ fetchImpl?: typeof fetch;
81
+ };
82
+
83
+ function readOauthToken(claudeConfigDir: string): string | null {
84
+ const tokenFile = join(claudeConfigDir, ".oauth-token");
85
+ if (!existsSync(tokenFile)) return null;
86
+ try {
87
+ const raw = readFileSync(tokenFile, "utf-8").trim();
88
+ return raw.length > 0 ? raw : null;
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+
94
+ function parseFloatHeader(headers: Headers, name: string): number | null {
95
+ const v = headers.get(name);
96
+ if (v == null || v.trim().length === 0) return null;
97
+ const n = Number(v);
98
+ return Number.isFinite(n) ? n : null;
99
+ }
100
+
101
+ function parseEpochHeader(headers: Headers, name: string): Date | null {
102
+ const v = headers.get(name);
103
+ if (v == null) return null;
104
+ const n = Number(v);
105
+ if (!Number.isFinite(n) || n <= 0) return null;
106
+ return new Date(n * 1000);
107
+ }
108
+
109
+ export function parseQuotaHeaders(headers: Headers): QuotaResult {
110
+ const fiveHour = parseFloatHeader(headers, "anthropic-ratelimit-unified-5h-utilization");
111
+ const sevenDay = parseFloatHeader(headers, "anthropic-ratelimit-unified-7d-utilization");
112
+ if (fiveHour == null && sevenDay == null) {
113
+ return {
114
+ ok: false,
115
+ reason: "no unified rate-limit headers in response (API token, not OAuth?)",
116
+ };
117
+ }
118
+ return {
119
+ ok: true,
120
+ data: {
121
+ fiveHourUtilizationPct: (fiveHour ?? 0) * 100,
122
+ sevenDayUtilizationPct: (sevenDay ?? 0) * 100,
123
+ fiveHourResetAt: parseEpochHeader(headers, "anthropic-ratelimit-unified-5h-reset"),
124
+ sevenDayResetAt: parseEpochHeader(headers, "anthropic-ratelimit-unified-7d-reset"),
125
+ representativeClaim: headers.get("anthropic-ratelimit-unified-representative-claim"),
126
+ overageStatus: headers.get("anthropic-ratelimit-unified-overage-status"),
127
+ overageDisabledReason: headers.get("anthropic-ratelimit-unified-overage-disabled-reason"),
128
+ },
129
+ };
130
+ }
131
+
132
+ export async function fetchQuota(opts: FetchQuotaOptions): Promise<QuotaResult> {
133
+ // Resolve the bearer token from either an explicit accessToken
134
+ // (account-level path) or by reading `.oauth-token` from a Claude
135
+ // config dir (legacy per-agent path). Reject if neither is set or
136
+ // both are — keep the API contract narrow.
137
+ let token: string | null;
138
+ if (opts.accessToken && opts.claudeConfigDir) {
139
+ return {
140
+ ok: false,
141
+ reason: "pass only one of `accessToken` or `claudeConfigDir`, not both",
142
+ };
143
+ }
144
+ if (opts.accessToken) {
145
+ token = opts.accessToken.trim().length > 0 ? opts.accessToken : null;
146
+ } else if (opts.claudeConfigDir) {
147
+ token = readOauthToken(opts.claudeConfigDir);
148
+ } else {
149
+ return {
150
+ ok: false,
151
+ reason: "fetchQuota requires `accessToken` or `claudeConfigDir`",
152
+ };
153
+ }
154
+ if (!token) {
155
+ return { ok: false, reason: "no OAuth token at .oauth-token" };
156
+ }
157
+
158
+ const controller = new AbortController();
159
+ const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 10_000);
160
+
161
+ const fetchFn = opts.fetchImpl ?? fetch;
162
+ let resp: Response;
163
+ try {
164
+ resp = await fetchFn("https://api.anthropic.com/v1/messages", {
165
+ method: "POST",
166
+ headers: {
167
+ "anthropic-version": "2023-06-01",
168
+ "anthropic-beta": OAUTH_BETA,
169
+ "authorization": `Bearer ${token}`,
170
+ "x-app": "cli",
171
+ "user-agent": DEFAULT_USER_AGENT,
172
+ "content-type": "application/json",
173
+ },
174
+ body: JSON.stringify({
175
+ model: opts.model ?? DEFAULT_PROBE_MODEL,
176
+ max_tokens: 1,
177
+ messages: [{ role: "user", content: "hi" }],
178
+ }),
179
+ signal: controller.signal,
180
+ });
181
+ } catch (err: unknown) {
182
+ const msg = (err as Error)?.message ?? String(err);
183
+ return { ok: false, reason: `request failed: ${msg}` };
184
+ } finally {
185
+ clearTimeout(timeout);
186
+ }
187
+
188
+ // We don't care whether the probe succeeded for message generation —
189
+ // Anthropic returns the rate-limit headers on both 2xx and rate-limited
190
+ // responses. Only bail if auth itself was rejected.
191
+ if (resp.status === 401 || resp.status === 403) {
192
+ return { ok: false, reason: `auth rejected (HTTP ${resp.status})` };
193
+ }
194
+
195
+ const parsed = parseQuotaHeaders(resp.headers);
196
+ if (!parsed.ok && resp.status >= 400) {
197
+ return { ok: false, reason: `HTTP ${resp.status}, ${parsed.reason}` };
198
+ }
199
+ return parsed;
200
+ }
201
+
202
+ /**
203
+ * Compact single-line representation for the session greeting.
204
+ * Example: "29% / 5h · 33% / 7d"
205
+ */
206
+ export function formatQuotaLine(q: QuotaUtilization): string {
207
+ const fmt = (n: number) => `${Math.round(n)}%`;
208
+ return `${fmt(q.fiveHourUtilizationPct)} / 5h · ${fmt(q.sevenDayUtilizationPct)} / 7d`;
209
+ }
210
+
211
+ /**
212
+ * Render a human-friendly "resets in …" countdown for a Date target.
213
+ * Exported so other surfaces (model-unavailable card, auth dashboard,
214
+ * banner helpers) speak the same dialect as `/usage`. Returns "—" for
215
+ * null targets and "resets now" once the target is in the past.
216
+ */
217
+ export function formatResetRelative(target: Date | null, now: Date = new Date()): string {
218
+ if (!target) return "—";
219
+ const deltaMs = target.getTime() - now.getTime();
220
+ if (deltaMs <= 0) return "resets now";
221
+ const totalMin = Math.round(deltaMs / 60_000);
222
+ if (totalMin < 60) return `resets in ${totalMin}m`;
223
+ const hours = Math.floor(totalMin / 60);
224
+ const mins = totalMin % 60;
225
+ if (hours < 24) return mins > 0 ? `resets in ${hours}h ${mins}m` : `resets in ${hours}h`;
226
+ const days = Math.floor(hours / 24);
227
+ const remH = hours % 24;
228
+ return remH > 0 ? `resets in ${days}d ${remH}h` : `resets in ${days}d`;
229
+ }
230
+
231
+ /**
232
+ * Multi-line Telegram HTML block for the /usage command. Shows both
233
+ * windows with their utilization percentages and reset countdowns,
234
+ * plus a representative-claim line if the server flagged one.
235
+ */
236
+ export function formatQuotaBlock(q: QuotaUtilization, now: Date = new Date()): string {
237
+ const lines: string[] = [];
238
+ lines.push("<b>Claude plan quota</b>");
239
+ lines.push("");
240
+ lines.push(
241
+ `<b>5h window</b> ${Math.round(q.fiveHourUtilizationPct)}% · ${formatResetRelative(q.fiveHourResetAt, now)}`,
242
+ );
243
+ lines.push(
244
+ `<b>7d window</b> ${Math.round(q.sevenDayUtilizationPct)}% · ${formatResetRelative(q.sevenDayResetAt, now)}`,
245
+ );
246
+ if (q.representativeClaim) {
247
+ lines.push("");
248
+ lines.push(`<i>Binding window: ${q.representativeClaim.replace(/_/g, " ")}</i>`);
249
+ }
250
+ if (q.overageStatus && q.overageStatus !== "allowed") {
251
+ const reason = q.overageDisabledReason ? ` (${q.overageDisabledReason})` : "";
252
+ lines.push(`<i>Overage: ${q.overageStatus}${reason}</i>`);
253
+ }
254
+ return lines.join("\n");
255
+ }
256
+
257
+ /* ── Account-level quota probe + short-lived cache ───────────────────── */
258
+
259
+ /**
260
+ * Resolve an account's OAuth access token from
261
+ * `~/.switchroom/accounts/<label>/credentials.json` (new account model
262
+ * — see `reference/share-auth-across-the-fleet.md`). Returns null when
263
+ * the file is missing, malformed, or has no accessToken — caller
264
+ * surfaces a graceful "missing credentials" badge.
265
+ *
266
+ * Exported for unit testing; production callers go through
267
+ * {@link fetchAccountQuota}.
268
+ */
269
+ export function readAccountAccessToken(
270
+ label: string,
271
+ home: string = (process.env.HOME ?? "/root"),
272
+ ): string | null {
273
+ const credPath = join(home, ".switchroom", "accounts", label, "credentials.json");
274
+ if (!existsSync(credPath)) return null;
275
+ try {
276
+ const raw = readFileSync(credPath, "utf-8");
277
+ const parsed = JSON.parse(raw) as {
278
+ claudeAiOauth?: { accessToken?: string };
279
+ };
280
+ const token = parsed.claudeAiOauth?.accessToken?.trim();
281
+ return token && token.length > 0 ? token : null;
282
+ } catch {
283
+ return null;
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Cache key per account label. The cached entry holds the result and
289
+ * the wall-clock timestamp it was fetched at, so the dashboard tap
290
+ * pattern (refresh-on-tap) doesn't trigger a fresh API call within the
291
+ * TTL window. Quota numbers don't move within a few seconds anyway.
292
+ */
293
+ type AccountQuotaCacheEntry = {
294
+ fetchedAt: number;
295
+ result: QuotaResult;
296
+ };
297
+
298
+ /** TTL for the per-account quota cache — controls when
299
+ * `prefetchAccountQuotaIfStale` re-probes Anthropic and when
300
+ * `fetchAccountQuota`'s cache-bypass kicks in. 5 min: quota numbers
301
+ * don't move within a few minutes for human-scale usage; the
302
+ * prefetch fires on every dashboard render so the cache stays fresh
303
+ * whenever the operator interacts. The dashboard's sync read
304
+ * (`getCachedAccountQuota`) returns last-known data regardless of
305
+ * staleness — see that function's docstring for why. */
306
+ export const ACCOUNT_QUOTA_CACHE_TTL_MS = 5 * 60_000;
307
+
308
+ const accountQuotaCache = new Map<string, AccountQuotaCacheEntry>();
309
+
310
+ /**
311
+ * Fetch quota for a global account by label. Wraps {@link fetchQuota}
312
+ * with token-resolution (`~/.switchroom/accounts/<label>/credentials.json`)
313
+ * and a short-lived in-process cache so repeat dashboard taps within
314
+ * the TTL don't re-hit the Anthropic API.
315
+ *
316
+ * Pass `force: true` to bypass the cache (used when the user
317
+ * explicitly taps "📊 Full quota" — they expect a live read).
318
+ */
319
+ export async function fetchAccountQuota(
320
+ label: string,
321
+ opts: {
322
+ home?: string;
323
+ force?: boolean;
324
+ now?: () => number;
325
+ fetchImpl?: typeof fetch;
326
+ timeoutMs?: number;
327
+ } = {},
328
+ ): Promise<QuotaResult> {
329
+ const now = opts.now?.() ?? Date.now();
330
+ if (!opts.force) {
331
+ const cached = accountQuotaCache.get(label);
332
+ if (cached && now - cached.fetchedAt < ACCOUNT_QUOTA_CACHE_TTL_MS) {
333
+ return cached.result;
334
+ }
335
+ }
336
+
337
+ const token = readAccountAccessToken(label, opts.home);
338
+ if (!token) {
339
+ const result: QuotaResult = {
340
+ ok: false,
341
+ reason: "no credentials.json or accessToken for account",
342
+ };
343
+ accountQuotaCache.set(label, { fetchedAt: now, result });
344
+ return result;
345
+ }
346
+
347
+ const result = await fetchQuota({
348
+ accessToken: token,
349
+ fetchImpl: opts.fetchImpl,
350
+ timeoutMs: opts.timeoutMs,
351
+ });
352
+ accountQuotaCache.set(label, { fetchedAt: now, result });
353
+ // Persist the snapshot to disk so a future gateway restart can
354
+ // re-hydrate its in-process cache without an API call. Best-effort
355
+ // (write errors swallowed inside writeAccountQuota). Issue #708.
356
+ if (result.ok) {
357
+ try {
358
+ writeAccountQuota(
359
+ label,
360
+ snapshotFromQuotaUtilization(result.data, new Date(now)),
361
+ );
362
+ } catch {
363
+ /* best-effort */
364
+ }
365
+ }
366
+ return result;
367
+ }
368
+
369
+ /**
370
+ * Re-hydrate the in-process account-quota cache from on-disk
371
+ * snapshots written by previous gateway lifetimes (issue #708).
372
+ * Called once at gateway boot so the boot card and the first /auth
373
+ * tap have data instantly — no need to wait for the background
374
+ * prefetch tick.
375
+ *
376
+ * Safe to call repeatedly: each label is set to the disk snapshot's
377
+ * `capturedAt` timestamp so a fresher live probe still wins on
378
+ * `now - fetchedAt < TTL` comparisons. When the disk snapshot is
379
+ * older than the TTL, the cache entry is still seeded — the background
380
+ * prefetch will replace it on the next tap.
381
+ */
382
+ export function hydrateAccountQuotaCacheFromDisk(
383
+ labels: ReadonlyArray<string>,
384
+ home?: string,
385
+ ): void {
386
+ for (const label of labels) {
387
+ if (accountQuotaCache.has(label)) continue;
388
+ const snap = readAccountQuota(label, home);
389
+ if (!snap) continue;
390
+ const fetchedAt = Date.parse(snap.capturedAt);
391
+ if (!Number.isFinite(fetchedAt)) continue;
392
+ const result: QuotaResult = {
393
+ ok: true,
394
+ data: {
395
+ fiveHourUtilizationPct: snap.fiveHourPct ?? 0,
396
+ sevenDayUtilizationPct: snap.sevenDayPct ?? 0,
397
+ fiveHourResetAt: snap.fiveHourResetAt ? new Date(snap.fiveHourResetAt) : null,
398
+ sevenDayResetAt: snap.sevenDayResetAt ? new Date(snap.sevenDayResetAt) : null,
399
+ representativeClaim: null,
400
+ overageStatus: null,
401
+ overageDisabledReason: null,
402
+ },
403
+ };
404
+ accountQuotaCache.set(label, { fetchedAt, result });
405
+ }
406
+ }
407
+
408
+ /** Test/utility helper — wipe the per-account quota cache. The
409
+ * gateway calls this on auth-account-level mutations (account add,
410
+ * account rm, account rename, refresh-accounts tick) so a stale
411
+ * pre-rename label doesn't survive into the dashboard. */
412
+ export function clearAccountQuotaCache(label?: string): void {
413
+ if (label == null) {
414
+ accountQuotaCache.clear();
415
+ return;
416
+ }
417
+ accountQuotaCache.delete(label);
418
+ }
419
+
420
+ /**
421
+ * Sync read of the account quota cache. Returns whatever's cached for
422
+ * this label — `null` only when there's NO entry at all. Stale-but-
423
+ * present cache entries are returned on purpose:
424
+ *
425
+ * - The dashboard renders sync; awaiting a fresh probe would block
426
+ * the user-visible message (and a probe can stall on Anthropic
427
+ * latency or network).
428
+ * - Showing yesterday's number is dramatically better UX than
429
+ * showing nothing — quota changes slowly enough that "the cached
430
+ * value" is almost always close to truth.
431
+ * - The background prefetch (`prefetchAccountQuotaIfStale`) keeps
432
+ * the cache fresh across renders. Within the 5-min TTL it
433
+ * no-ops; past the TTL it kicks off a fresh probe whose result
434
+ * is visible on the operator's NEXT render (refresh tap or
435
+ * auto-refresh after an action).
436
+ *
437
+ * Pre-v0.6.11 this function treated stale entries as a miss, which
438
+ * meant the boot-warmed cache vanished after 30s and the operator
439
+ * saw empty quota rows on the first /auth tap of any day after the
440
+ * gateway restart. That's the bug this docstring exists to keep
441
+ * fixed.
442
+ */
443
+ export function getCachedAccountQuota(
444
+ _label: string,
445
+ _now: number = Date.now(),
446
+ ): QuotaResult | null {
447
+ // Note the unused params — we keep the signature stable for callers
448
+ // that pass `now` (test helpers) even though we no longer use it.
449
+ const cached = accountQuotaCache.get(_label);
450
+ if (!cached) return null;
451
+ return cached.result;
452
+ }
453
+
454
+ /**
455
+ * Fire-and-forget background prefetch — kicks off
456
+ * `fetchAccountQuota` if the cache is cold/stale and discards the
457
+ * promise. Safe to call on every dashboard render: the cache TTL
458
+ * keeps the API call rate bounded to ~1 per account per 30s
459
+ * regardless of how many times the user taps /auth.
460
+ *
461
+ * Errors are swallowed (the next tap re-tries via the cache miss
462
+ * path); the dashboard's empty quota row is the user-visible
463
+ * "didn't probe yet" signal.
464
+ */
465
+ export function prefetchAccountQuotaIfStale(
466
+ label: string,
467
+ opts: { home?: string; now?: () => number; fetchImpl?: typeof fetch } = {},
468
+ ): void {
469
+ const now = opts.now?.() ?? Date.now();
470
+ const cached = accountQuotaCache.get(label);
471
+ if (cached && now - cached.fetchedAt < ACCOUNT_QUOTA_CACHE_TTL_MS) return;
472
+ // Don't await — background warm.
473
+ void fetchAccountQuota(label, opts).catch(() => {});
474
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Outbound dedup window (#546).
3
+ *
4
+ * Closes the duplicate-reply class where:
5
+ *
6
+ * 1. Agent emits text that the gateway buffers as PTY-tail.
7
+ * 2. Bridge disconnects mid-flight (turn boundary, restart, etc.)
8
+ * before claude-code's reply / stream_reply tool_call lands.
9
+ * 3. Gateway's `turn-flush` backstop fires after a 500ms grace —
10
+ * sees no reply tool_call landed, sends the buffered text as
11
+ * its own message (HTML-rendered).
12
+ * 4. claude-code preserves the un-acked tool_call in its session
13
+ * and replays it on the next bridge connection. The replayed
14
+ * stream_reply lands as a SECOND message with the same content
15
+ * (raw markdown, since reply tools don't always render HTML).
16
+ *
17
+ * Smoking-gun evidence: klanker chat 8248703757, msgs 5025 + 5027,
18
+ * 11s apart. msg=5025 had `<b>...</b>` (turn-flush + markdownToHtml).
19
+ * msg=5027 had `**...**` (the raw markdown reply tool's payload).
20
+ * Same content, different formatting, two messages.
21
+ *
22
+ * Fix shape: maintain a small in-memory cache of "what we just sent"
23
+ * keyed by (chatId, threadId). Before any reply / stream_reply
24
+ * actually sends, check whether a recent outbound matches the
25
+ * normalised content. If so, skip the send and return a successful
26
+ * tool-call result so claude-code's retry loop closes cleanly.
27
+ *
28
+ * This is a SAFETY NET layer. The cleaner architectural fix would be
29
+ * "don't fire turn-flush when claude-code might retry" — but that
30
+ * needs reliable detection of the retry intent ahead of time, which
31
+ * we don't have. Dedup-after-the-fact is robust against the full
32
+ * range of "two paths, same content" failure modes.
33
+ *
34
+ * Pure module: no I/O, no globals, no clock reads beyond the caller-
35
+ * supplied `now`. Fully unit-testable.
36
+ */
37
+
38
+ /** TTL after which a recorded outbound is forgotten. 60s catches the
39
+ * typical retry window (we've seen 9-11s in the wild) with margin
40
+ * for slower networks and avoids deduping legitimate later replies
41
+ * that happen to repeat content. */
42
+ export const DEFAULT_DEDUP_TTL_MS = 60_000
43
+
44
+ /** Minimum content length below which we don't bother deduping.
45
+ * Short replies (<= 24 chars) like "ok", "got it", "✅" frequently
46
+ * recur within seconds in normal multi-turn conversation; deduping
47
+ * them would suppress legitimate repeats. The bug class we're
48
+ * defending against involves multi-paragraph content, so the floor
49
+ * is conservative. */
50
+ export const DEDUP_MIN_CONTENT_LEN = 24
51
+
52
+ interface DedupEntry {
53
+ /** Normalized content hash (see `normalizeForDedup`). */
54
+ hash: string
55
+ /** Wall-clock ms when recorded. */
56
+ ts: number
57
+ /** First 80 chars of the original (un-normalized) text — for
58
+ * operator-facing log lines that show what got deduped. */
59
+ preview: string
60
+ }
61
+
62
+ /**
63
+ * In-memory dedup cache, keyed by `chatId|threadId`. Bounded by
64
+ * TTL eviction on every read; we don't cap entries because chat
65
+ * count per gateway is small (one per active conversation).
66
+ */
67
+ export class OutboundDedupCache {
68
+ private readonly entries = new Map<string, DedupEntry[]>()
69
+ private readonly ttlMs: number
70
+
71
+ constructor(opts: { ttlMs?: number } = {}) {
72
+ this.ttlMs = opts.ttlMs ?? DEFAULT_DEDUP_TTL_MS
73
+ }
74
+
75
+ /** Record an outbound message. Caller should invoke this after a
76
+ * successful send, regardless of which path sent it (turn-flush,
77
+ * executeReply, executeStreamReply, etc.). Short content is not
78
+ * recorded — see DEDUP_MIN_CONTENT_LEN. */
79
+ record(chatId: string, threadId: number | undefined, text: string, now: number): void {
80
+ if (text.length < DEDUP_MIN_CONTENT_LEN) return
81
+ const key = makeKey(chatId, threadId)
82
+ const list = this.entries.get(key) ?? []
83
+ this.evict(list, now)
84
+ list.push({
85
+ hash: normalizeForDedup(text),
86
+ ts: now,
87
+ preview: text.slice(0, 80),
88
+ })
89
+ this.entries.set(key, list)
90
+ }
91
+
92
+ /** Check whether the given text was already sent recently to the
93
+ * same chat. Returns the matched entry's preview + age on hit, or
94
+ * null on miss. Caller decides what to do with the answer
95
+ * (skip-send, log, etc.). */
96
+ check(
97
+ chatId: string,
98
+ threadId: number | undefined,
99
+ text: string,
100
+ now: number,
101
+ ): { matched: true; preview: string; ageMs: number } | null {
102
+ if (text.length < DEDUP_MIN_CONTENT_LEN) return null
103
+ const key = makeKey(chatId, threadId)
104
+ const list = this.entries.get(key)
105
+ if (!list) return null
106
+ this.evict(list, now)
107
+ const candidateHash = normalizeForDedup(text)
108
+ for (const entry of list) {
109
+ if (entry.hash === candidateHash) {
110
+ return { matched: true, preview: entry.preview, ageMs: now - entry.ts }
111
+ }
112
+ }
113
+ return null
114
+ }
115
+
116
+ /** Test-only: clear all entries. */
117
+ clear(): void {
118
+ this.entries.clear()
119
+ }
120
+
121
+ /** Test-only: count of live entries (post-eviction). */
122
+ size(now: number): number {
123
+ let total = 0
124
+ for (const list of this.entries.values()) {
125
+ this.evict(list, now)
126
+ total += list.length
127
+ }
128
+ return total
129
+ }
130
+
131
+ private evict(list: DedupEntry[], now: number): void {
132
+ const cutoff = now - this.ttlMs
133
+ let i = 0
134
+ while (i < list.length && list[i].ts < cutoff) i++
135
+ if (i > 0) list.splice(0, i)
136
+ }
137
+ }
138
+
139
+ function makeKey(chatId: string, threadId: number | undefined): string {
140
+ return threadId == null ? chatId : `${chatId}|${threadId}`
141
+ }
142
+
143
+ /**
144
+ * Normalise text for content equality. The bug we're defending
145
+ * against produces the SAME content rendered two different ways:
146
+ * one path runs `markdownToHtml` (so `**foo**` becomes `<b>foo</b>`),
147
+ * the other doesn't. Both must hash identically.
148
+ *
149
+ * Steps:
150
+ * 1. Strip HTML tags (`<b>foo</b>` → `foo`).
151
+ * 2. Strip markdown markers (`**foo**` / `__foo__` / `` `foo` `` → `foo`).
152
+ * 3. Collapse all whitespace to single space + trim.
153
+ * 4. Lowercase (defensive — both renderers preserve case but a
154
+ * future formatter might title-case headings, etc.).
155
+ *
156
+ * NOT a hash function in the cryptographic sense — just a
157
+ * normalised-string comparison key. Identical content + identical
158
+ * normaliser → identical key.
159
+ */
160
+ export function normalizeForDedup(text: string): string {
161
+ return text
162
+ .replace(/<\/?[a-zA-Z][^>]*>/g, '') // HTML tags
163
+ .replace(/&[a-zA-Z]+;|&#\d+;/g, ' ') // HTML entities → space
164
+ .replace(/(\*\*|__|`)+/g, '') // markdown bold/italic/code markers
165
+ .replace(/^[#>\-*+]\s+/gm, '') // markdown line prefixes
166
+ .replace(/\s+/g, ' ')
167
+ .trim()
168
+ .toLowerCase()
169
+ }