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.
- package/LICENSE +21 -0
- package/README.md +447 -0
- package/bin/autoaccept.exp +81 -0
- package/bin/boot-self-test.sh +149 -0
- package/bin/bridge-watchdog.sh +967 -0
- package/bin/handoff-briefing.sh +206 -0
- package/bin/run-hook.sh +228 -0
- package/bin/switchroom.ts +4 -0
- package/bin/timezone-hook.sh +67 -0
- package/bin/user-profile-refresh-hook.sh +38 -0
- package/bin/workspace-dynamic-hook.sh +142 -0
- package/bin/workspace-stable-hook.sh +57 -0
- package/dist/cli/autoaccept-poll.js +118 -0
- package/dist/cli/switchroom.js +48557 -0
- package/package.json +95 -0
- package/profiles/_base/settings.json.hbs +15 -0
- package/profiles/_base/start.sh.hbs +383 -0
- package/profiles/_shared/telegram-style.md.hbs +140 -0
- package/profiles/coding/CLAUDE.md.hbs +57 -0
- package/profiles/coding/skills/architecture/SKILL.md +70 -0
- package/profiles/coding/skills/code-review/SKILL.md +58 -0
- package/profiles/coding/workspace/SOUL.md.hbs +25 -0
- package/profiles/default/CLAUDE.md +238 -0
- package/profiles/default/CLAUDE.md.hbs +113 -0
- package/profiles/default/workspace/CLAUDE.md.hbs +126 -0
- package/profiles/default/workspace/HEARTBEAT.md.hbs +40 -0
- package/profiles/default/workspace/IDENTITY.md.hbs +32 -0
- package/profiles/default/workspace/MEMORY.md.hbs +29 -0
- package/profiles/default/workspace/SOUL.md.hbs +61 -0
- package/profiles/default/workspace/TOOLS.md.hbs +29 -0
- package/profiles/default/workspace/USER.md.hbs +52 -0
- package/profiles/default/workspace/memory/.gitkeep +0 -0
- package/profiles/executive-assistant/CLAUDE.md.hbs +51 -0
- package/profiles/executive-assistant/skills/daily-briefing/SKILL.md +55 -0
- package/profiles/executive-assistant/skills/meeting-prep/SKILL.md +58 -0
- package/profiles/executive-assistant/workspace/SOUL.md.hbs +25 -0
- package/profiles/health-coach/CLAUDE.md.hbs +45 -0
- package/profiles/health-coach/skills/check-in/SKILL.md +41 -0
- package/profiles/health-coach/skills/weekly-review/SKILL.md +53 -0
- package/profiles/health-coach/workspace/SOUL.md.hbs +25 -0
- package/skills/buildkite-agent-infrastructure/SKILL.md +302 -0
- package/skills/buildkite-agent-infrastructure/agents/openai.yaml +6 -0
- package/skills/buildkite-agent-infrastructure/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-agent-infrastructure/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-agent-infrastructure/references/audit-logging.md +87 -0
- package/skills/buildkite-agent-infrastructure/references/graphql-mutations.md +690 -0
- package/skills/buildkite-agent-infrastructure/references/instance-shapes.md +38 -0
- package/skills/buildkite-agent-infrastructure/references/pipeline-templates.md +73 -0
- package/skills/buildkite-agent-infrastructure/references/self-hosted-agents.md +137 -0
- package/skills/buildkite-agent-infrastructure/references/sso-saml.md +92 -0
- package/skills/buildkite-agent-runtime/SKILL.md +476 -0
- package/skills/buildkite-agent-runtime/agents/openai.yaml +6 -0
- package/skills/buildkite-agent-runtime/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-agent-runtime/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-agent-runtime/references/flag-reference.md +417 -0
- package/skills/buildkite-agent-runtime/references/patterns-and-recipes.md +555 -0
- package/skills/buildkite-api/SKILL.md +285 -0
- package/skills/buildkite-api/agents/openai.yaml +6 -0
- package/skills/buildkite-api/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-api/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-api/references/graphql-reference.md +195 -0
- package/skills/buildkite-api/references/patterns.md +44 -0
- package/skills/buildkite-api/references/webhooks.md +161 -0
- package/skills/buildkite-cli/SKILL.md +379 -0
- package/skills/buildkite-cli/agents/openai.yaml +6 -0
- package/skills/buildkite-cli/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-cli/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-cli/references/command-reference.md +181 -0
- package/skills/buildkite-migration/SKILL.md +182 -0
- package/skills/buildkite-pipelines/SKILL.md +464 -0
- package/skills/buildkite-pipelines/agents/openai.yaml +6 -0
- package/skills/buildkite-pipelines/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-pipelines/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-pipelines/examples/basic-pipeline.yml +24 -0
- package/skills/buildkite-pipelines/examples/optimized-pipeline.yml +100 -0
- package/skills/buildkite-pipelines/references/advanced-patterns.md +286 -0
- package/skills/buildkite-pipelines/references/retry-and-error-codes.md +131 -0
- package/skills/buildkite-pipelines/references/step-types-reference.md +225 -0
- package/skills/buildkite-secure-delivery/SKILL.md +168 -0
- package/skills/buildkite-secure-delivery/agents/openai.yaml +6 -0
- package/skills/buildkite-secure-delivery/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-secure-delivery/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-secure-delivery/references/oidc-cloud-providers.md +83 -0
- package/skills/buildkite-secure-delivery/references/package-publishing.md +100 -0
- package/skills/buildkite-test-engine/SKILL.md +239 -0
- package/skills/buildkite-test-engine/agents/openai.yaml +6 -0
- package/skills/buildkite-test-engine/assets/buildkite-icon-large.png +0 -0
- package/skills/buildkite-test-engine/assets/buildkite-icon-small.png +0 -0
- package/skills/buildkite-test-engine/examples/bktec-splitting.yml +16 -0
- package/skills/buildkite-test-engine/examples/collector-pipeline.yml +11 -0
- package/skills/buildkite-test-engine/references/collectors.md +198 -0
- package/skills/buildkite-test-engine/references/splitting-examples.md +93 -0
- package/skills/docx/LICENSE.txt +30 -0
- package/skills/docx/SKILL.md +590 -0
- package/skills/docx/VENDORED.md +32 -0
- package/skills/docx/scripts/__init__.py +1 -0
- package/skills/docx/scripts/accept_changes.py +135 -0
- package/skills/docx/scripts/comment.py +318 -0
- package/skills/docx/scripts/office/helpers/__init__.py +0 -0
- package/skills/docx/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/docx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/docx/scripts/office/pack.py +159 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/docx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/docx/scripts/office/soffice.py +183 -0
- package/skills/docx/scripts/office/unpack.py +132 -0
- package/skills/docx/scripts/office/validate.py +111 -0
- package/skills/docx/scripts/office/validators/__init__.py +15 -0
- package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-313.pyc +0 -0
- package/skills/docx/scripts/office/validators/__pycache__/base.cpython-313.pyc +0 -0
- package/skills/docx/scripts/office/validators/base.py +847 -0
- package/skills/docx/scripts/office/validators/docx.py +446 -0
- package/skills/docx/scripts/office/validators/pptx.py +275 -0
- package/skills/docx/scripts/office/validators/redlining.py +247 -0
- package/skills/docx/scripts/templates/comments.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/skills/docx/scripts/templates/people.xml +3 -0
- package/skills/file-bug/SKILL.md +129 -0
- package/skills/humanizer/LICENSE +21 -0
- package/skills/humanizer/SKILL.md +559 -0
- package/skills/humanizer/VENDORED.md +38 -0
- package/skills/humanizer-calibrate/SKILL.md +144 -0
- package/skills/mcp-builder/LICENSE.txt +202 -0
- package/skills/mcp-builder/SKILL.md +236 -0
- package/skills/mcp-builder/VENDORED.md +32 -0
- package/skills/mcp-builder/reference/evaluation.md +602 -0
- package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/skills/mcp-builder/scripts/connections.py +151 -0
- package/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/skills/pdf/LICENSE.txt +30 -0
- package/skills/pdf/SKILL.md +314 -0
- package/skills/pdf/VENDORED.md +32 -0
- package/skills/pdf/forms.md +294 -0
- package/skills/pdf/reference.md +612 -0
- package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
- package/skills/pdf/scripts/check_fillable_fields.py +11 -0
- package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
- package/skills/pdf/scripts/create_validation_image.py +37 -0
- package/skills/pdf/scripts/extract_form_field_info.py +122 -0
- package/skills/pdf/scripts/extract_form_structure.py +115 -0
- package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
- package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/skills/pptx/LICENSE.txt +30 -0
- package/skills/pptx/SKILL.md +232 -0
- package/skills/pptx/VENDORED.md +32 -0
- package/skills/pptx/editing.md +205 -0
- package/skills/pptx/pptxgenjs.md +420 -0
- package/skills/pptx/scripts/__init__.py +0 -0
- package/skills/pptx/scripts/add_slide.py +195 -0
- package/skills/pptx/scripts/clean.py +286 -0
- package/skills/pptx/scripts/office/helpers/__init__.py +0 -0
- package/skills/pptx/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/pptx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/pptx/scripts/office/pack.py +159 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/pptx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/pptx/scripts/office/soffice.py +183 -0
- package/skills/pptx/scripts/office/unpack.py +132 -0
- package/skills/pptx/scripts/office/validate.py +111 -0
- package/skills/pptx/scripts/office/validators/__init__.py +15 -0
- package/skills/pptx/scripts/office/validators/base.py +847 -0
- package/skills/pptx/scripts/office/validators/docx.py +446 -0
- package/skills/pptx/scripts/office/validators/pptx.py +275 -0
- package/skills/pptx/scripts/office/validators/redlining.py +247 -0
- package/skills/pptx/scripts/thumbnail.py +289 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +485 -0
- package/skills/skill-creator/VENDORED.md +32 -0
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +326 -0
- package/skills/skill-creator/scripts/improve_description.py +247 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +310 -0
- package/skills/skill-creator/scripts/run_loop.py +328 -0
- package/skills/skill-creator/scripts/utils.py +47 -0
- package/skills/switchroom-architecture/SKILL.md +60 -0
- package/skills/switchroom-architecture/cascade.md +112 -0
- package/skills/switchroom-architecture/sub-agents.md +87 -0
- package/skills/switchroom-architecture/telegram.md +94 -0
- package/skills/switchroom-cli/SKILL.md +274 -0
- package/skills/switchroom-health/SKILL.md +101 -0
- package/skills/switchroom-install/SKILL.md +116 -0
- package/skills/switchroom-manage/SKILL.md +90 -0
- package/skills/switchroom-status/SKILL.md +69 -0
- package/skills/switchroom-status/scripts/status.sh +69 -0
- package/skills/telegram-test-harness/SKILL.md +191 -0
- package/skills/token-helpers/SKILL.md +73 -0
- package/skills/token-helpers/scripts/google-cal-token.sh +62 -0
- package/skills/token-helpers/scripts/ms-graph-token.sh +70 -0
- package/skills/webapp-testing/LICENSE.txt +202 -0
- package/skills/webapp-testing/SKILL.md +96 -0
- package/skills/webapp-testing/VENDORED.md +32 -0
- package/skills/webapp-testing/examples/console_logging.py +35 -0
- package/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/skills/webapp-testing/scripts/with_server.py +106 -0
- package/skills/xlsx/LICENSE.txt +30 -0
- package/skills/xlsx/SKILL.md +292 -0
- package/skills/xlsx/VENDORED.md +32 -0
- package/skills/xlsx/scripts/office/helpers/__init__.py +0 -0
- package/skills/xlsx/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/xlsx/scripts/office/pack.py +159 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/xlsx/scripts/office/soffice.py +183 -0
- package/skills/xlsx/scripts/office/unpack.py +132 -0
- package/skills/xlsx/scripts/office/validate.py +111 -0
- package/skills/xlsx/scripts/office/validators/__init__.py +15 -0
- package/skills/xlsx/scripts/office/validators/base.py +847 -0
- package/skills/xlsx/scripts/office/validators/docx.py +446 -0
- package/skills/xlsx/scripts/office/validators/pptx.py +275 -0
- package/skills/xlsx/scripts/office/validators/redlining.py +247 -0
- package/skills/xlsx/scripts/recalc.py +184 -0
- package/telegram-plugin/.claude-plugin/plugin.json +20 -0
- package/telegram-plugin/.mcp.json +14 -0
- package/telegram-plugin/LICENSE +21 -0
- package/telegram-plugin/README.md +352 -0
- package/telegram-plugin/active-pins-sweep.ts +204 -0
- package/telegram-plugin/active-pins.ts +146 -0
- package/telegram-plugin/active-reactions-sweep.ts +79 -0
- package/telegram-plugin/active-reactions.ts +134 -0
- package/telegram-plugin/admin-commands/dispatch.test.ts +149 -0
- package/telegram-plugin/admin-commands/index.ts +106 -0
- package/telegram-plugin/answer-stream.ts +565 -0
- package/telegram-plugin/ask-user.ts +179 -0
- package/telegram-plugin/attachment-path.ts +80 -0
- package/telegram-plugin/auth-code-redact.ts +83 -0
- package/telegram-plugin/auth-dashboard.ts +1104 -0
- package/telegram-plugin/auth-slot-parser.ts +497 -0
- package/telegram-plugin/auto-fallback-dispatcher.ts +68 -0
- package/telegram-plugin/auto-fallback.ts +348 -0
- package/telegram-plugin/bridge/bridge.ts +687 -0
- package/telegram-plugin/bridge/ipc-client.ts +326 -0
- package/telegram-plugin/bun.lock +218 -0
- package/telegram-plugin/card-format.ts +62 -0
- package/telegram-plugin/channel-envelope-safety.test.ts +56 -0
- package/telegram-plugin/channel-envelope-safety.ts +56 -0
- package/telegram-plugin/chat-lock.ts +65 -0
- package/telegram-plugin/context-exhaustion.ts +38 -0
- package/telegram-plugin/credits-watch.ts +220 -0
- package/telegram-plugin/dist/bridge/bridge.js +24758 -0
- package/telegram-plugin/dist/foreman/foreman.js +30723 -0
- package/telegram-plugin/dist/gateway/gateway.js +46497 -0
- package/telegram-plugin/dist/server.js +24551 -0
- package/telegram-plugin/dm-command-gate.ts +56 -0
- package/telegram-plugin/docs/gateway-server-split.md +133 -0
- package/telegram-plugin/docs/multi-agent-card-design.md +847 -0
- package/telegram-plugin/docs/pinned-progress-card-reliability.md +144 -0
- package/telegram-plugin/docs/stream-json-daemon-mode.md +477 -0
- package/telegram-plugin/docs/waiting-ux-spec.md +233 -0
- package/telegram-plugin/draft-stream.ts +442 -0
- package/telegram-plugin/draft-transport.ts +72 -0
- package/telegram-plugin/first-paint.ts +246 -0
- package/telegram-plugin/fleet-state.ts +246 -0
- package/telegram-plugin/foreman/foreman-create-flow.ts +202 -0
- package/telegram-plugin/foreman/foreman-handlers.ts +493 -0
- package/telegram-plugin/foreman/foreman.ts +1130 -0
- package/telegram-plugin/foreman/setup-flow.ts +345 -0
- package/telegram-plugin/foreman/setup-state.ts +239 -0
- package/telegram-plugin/foreman/state.ts +203 -0
- package/telegram-plugin/format.ts +685 -0
- package/telegram-plugin/gateway/access-validator.test.ts +95 -0
- package/telegram-plugin/gateway/access-validator.ts +37 -0
- package/telegram-plugin/gateway/boot-card.ts +582 -0
- package/telegram-plugin/gateway/boot-probes.ts +863 -0
- package/telegram-plugin/gateway/boot-reason.ts +51 -0
- package/telegram-plugin/gateway/boot-sweep-filter.test.ts +54 -0
- package/telegram-plugin/gateway/boot-sweep-filter.ts +32 -0
- package/telegram-plugin/gateway/clean-shutdown-marker.ts +183 -0
- package/telegram-plugin/gateway/disconnect-flush.ts +109 -0
- package/telegram-plugin/gateway/gateway.ts +10202 -0
- package/telegram-plugin/gateway/inbound-coalesce.ts +147 -0
- package/telegram-plugin/gateway/inject-handler.test.ts +221 -0
- package/telegram-plugin/gateway/inject-handler.ts +190 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +151 -0
- package/telegram-plugin/gateway/ipc-server.ts +494 -0
- package/telegram-plugin/gateway/pid-file.ts +103 -0
- package/telegram-plugin/gateway/poll-health.ts +156 -0
- package/telegram-plugin/gateway/preamble-suppressor.ts +154 -0
- package/telegram-plugin/gateway/quota-cache.ts +125 -0
- package/telegram-plugin/gateway/resolve-calling-subagent.ts +78 -0
- package/telegram-plugin/gateway/restart-watchdog.ts +200 -0
- package/telegram-plugin/gateway/session-marker.ts +83 -0
- package/telegram-plugin/gateway/shutdown-drain.ts +162 -0
- package/telegram-plugin/gateway/startup-mutex.ts +285 -0
- package/telegram-plugin/gateway/startup-network-retry.ts +142 -0
- package/telegram-plugin/gateway/turn-active-marker.ts +176 -0
- package/telegram-plugin/gateway/unhandled-rejection-policy.ts +78 -0
- package/telegram-plugin/handoff-continuity.ts +200 -0
- package/telegram-plugin/history.ts +468 -0
- package/telegram-plugin/hooks/hooks.json +58 -0
- package/telegram-plugin/hooks/secret-guard-pretool.mjs +208 -0
- package/telegram-plugin/hooks/secret-scrub-stop.mjs +98 -0
- package/telegram-plugin/hooks/silent-end-interrupt-stop.mjs +111 -0
- package/telegram-plugin/hooks/subagent-tracker-posttool.mjs +296 -0
- package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +261 -0
- package/telegram-plugin/html-sanitize.ts +244 -0
- package/telegram-plugin/idle-footer.ts +65 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +166 -0
- package/telegram-plugin/interrupt-marker.ts +66 -0
- package/telegram-plugin/issues-card.ts +371 -0
- package/telegram-plugin/issues-watcher.ts +125 -0
- package/telegram-plugin/model-unavailable.ts +325 -0
- package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/telegram-plugin/operator-events-history.ts +94 -0
- package/telegram-plugin/operator-events.fixtures.json +161 -0
- package/telegram-plugin/operator-events.ts +421 -0
- package/telegram-plugin/package.json +55 -0
- package/telegram-plugin/permission-rule.ts +133 -0
- package/telegram-plugin/permission-title.ts +117 -0
- package/telegram-plugin/pin-event-log.ts +76 -0
- package/telegram-plugin/plugin-logger.ts +136 -0
- package/telegram-plugin/progress-card-driver.ts +2697 -0
- package/telegram-plugin/progress-card-pin-manager.ts +589 -0
- package/telegram-plugin/progress-card-pin-watchdog.ts +98 -0
- package/telegram-plugin/progress-card.ts +1409 -0
- package/telegram-plugin/pty-partial-handler.ts +247 -0
- package/telegram-plugin/pty-tail.ts +730 -0
- package/telegram-plugin/quota-check.ts +474 -0
- package/telegram-plugin/recent-outbound-dedup.ts +169 -0
- package/telegram-plugin/registry/api-registry.test.ts +201 -0
- package/telegram-plugin/registry/subagents-bugs.test.ts +454 -0
- package/telegram-plugin/registry/subagents-schema.ts +509 -0
- package/telegram-plugin/registry/subagents.test.ts +476 -0
- package/telegram-plugin/registry/turns-schema.test.ts +101 -0
- package/telegram-plugin/registry/turns-schema.ts +417 -0
- package/telegram-plugin/retry-api-call.ts +172 -0
- package/telegram-plugin/scripts/build.mjs +78 -0
- package/telegram-plugin/secret-detect/audit.ts +66 -0
- package/telegram-plugin/secret-detect/chunker.ts +37 -0
- package/telegram-plugin/secret-detect/entropy.ts +20 -0
- package/telegram-plugin/secret-detect/gitleaks-loader.ts +74 -0
- package/telegram-plugin/secret-detect/gitleaks.toml +27 -0
- package/telegram-plugin/secret-detect/index.ts +218 -0
- package/telegram-plugin/secret-detect/kv-scanner.ts +60 -0
- package/telegram-plugin/secret-detect/mask.ts +13 -0
- package/telegram-plugin/secret-detect/patterns.ts +115 -0
- package/telegram-plugin/secret-detect/pipeline.ts +144 -0
- package/telegram-plugin/secret-detect/rewrite.ts +26 -0
- package/telegram-plugin/secret-detect/secretlint-source.ts +95 -0
- package/telegram-plugin/secret-detect/slug.ts +44 -0
- package/telegram-plugin/secret-detect/staging.ts +85 -0
- package/telegram-plugin/secret-detect/suppressor.ts +34 -0
- package/telegram-plugin/secret-detect/url-redact.ts +60 -0
- package/telegram-plugin/secret-detect/vault-write.ts +56 -0
- package/telegram-plugin/server.js +41795 -0
- package/telegram-plugin/server.ts +171 -0
- package/telegram-plugin/session-tail.ts +884 -0
- package/telegram-plugin/shared/bot-runtime.ts +324 -0
- package/telegram-plugin/silent-reply.ts +58 -0
- package/telegram-plugin/slot-banner-driver.ts +147 -0
- package/telegram-plugin/slot-banner.ts +86 -0
- package/telegram-plugin/start.js +26 -0
- package/telegram-plugin/startup-reset.ts +45 -0
- package/telegram-plugin/status-reactions.ts +332 -0
- package/telegram-plugin/steering.ts +155 -0
- package/telegram-plugin/sticker-aliases.ts +249 -0
- package/telegram-plugin/stream-controller.ts +311 -0
- package/telegram-plugin/stream-reply-handler.ts +664 -0
- package/telegram-plugin/streaming-metrics.ts +134 -0
- package/telegram-plugin/streaming-report.ts +204 -0
- package/telegram-plugin/subagent-watcher.ts +880 -0
- package/telegram-plugin/telegram-button-constraints.ts +191 -0
- package/telegram-plugin/telegraph.ts +381 -0
- package/telegram-plugin/tests/HARNESS.md +340 -0
- package/telegram-plugin/tests/_progress-card-harness.ts +105 -0
- package/telegram-plugin/tests/active-pins-boot-reaper.test.ts +211 -0
- package/telegram-plugin/tests/active-pins-sweep.test.ts +309 -0
- package/telegram-plugin/tests/active-pins.test.ts +187 -0
- package/telegram-plugin/tests/active-reactions-sweep.test.ts +116 -0
- package/telegram-plugin/tests/active-reactions.test.ts +198 -0
- package/telegram-plugin/tests/answer-stream-dedup.test.ts +352 -0
- package/telegram-plugin/tests/answer-stream-silent-markers.test.ts +236 -0
- package/telegram-plugin/tests/answer-stream.test.ts +878 -0
- package/telegram-plugin/tests/ask-user.test.ts +203 -0
- package/telegram-plugin/tests/attachment-path.test.ts +199 -0
- package/telegram-plugin/tests/auth-account-identity-surface.test.ts +118 -0
- package/telegram-plugin/tests/auth-code-auto-capture.test.ts +144 -0
- package/telegram-plugin/tests/auth-code-redact.test.ts +248 -0
- package/telegram-plugin/tests/auth-dashboard-edge-cases.test.ts +260 -0
- package/telegram-plugin/tests/auth-dashboard-restart-flow.test.ts +140 -0
- package/telegram-plugin/tests/auth-dashboard-v3b.test.ts +559 -0
- package/telegram-plugin/tests/auth-dashboard.test.ts +1045 -0
- package/telegram-plugin/tests/auth-login-url-button.test.ts +122 -0
- package/telegram-plugin/tests/auth-slot-commands.test.ts +640 -0
- package/telegram-plugin/tests/auto-fallback-dispatcher.e2e.test.ts +183 -0
- package/telegram-plugin/tests/auto-fallback.test.ts +381 -0
- package/telegram-plugin/tests/boot-card-account-quota.test.ts +137 -0
- package/telegram-plugin/tests/boot-card-dedupe.test.ts +154 -0
- package/telegram-plugin/tests/boot-card-probe-target.test.ts +194 -0
- package/telegram-plugin/tests/boot-card-reason.test.ts +103 -0
- package/telegram-plugin/tests/boot-card-render.test.ts +219 -0
- package/telegram-plugin/tests/boot-probes.test.ts +451 -0
- package/telegram-plugin/tests/bot-api.harness.ts +116 -0
- package/telegram-plugin/tests/bot-runtime.test.ts +190 -0
- package/telegram-plugin/tests/bridge-anonymous-refuse.test.ts +60 -0
- package/telegram-plugin/tests/context-exhaustion.test.ts +114 -0
- package/telegram-plugin/tests/credits-watch.test.ts +221 -0
- package/telegram-plugin/tests/dm-command-gate.test.ts +176 -0
- package/telegram-plugin/tests/draft-stream.test.ts +752 -0
- package/telegram-plugin/tests/draft-transport.test.ts +141 -0
- package/telegram-plugin/tests/e2e.test.ts +436 -0
- package/telegram-plugin/tests/fake-bot-api.test.ts +213 -0
- package/telegram-plugin/tests/fake-bot-api.ts +617 -0
- package/telegram-plugin/tests/false-restart-banner.test.ts +253 -0
- package/telegram-plugin/tests/first-paint.test.ts +257 -0
- package/telegram-plugin/tests/fixtures/pty-tail-tmux-fragment.bin +6 -0
- package/telegram-plugin/tests/fixtures/service-log-current-claude-code.bin +3624 -0
- package/telegram-plugin/tests/fleet-state-watcher.test.ts +101 -0
- package/telegram-plugin/tests/fleet-state.test.ts +185 -0
- package/telegram-plugin/tests/foreman-create-flow.test.ts +359 -0
- package/telegram-plugin/tests/foreman-handlers.test.ts +347 -0
- package/telegram-plugin/tests/foreman-state.test.ts +164 -0
- package/telegram-plugin/tests/foreman-write-ops.test.ts +214 -0
- package/telegram-plugin/tests/gateway-409-retry-banner.test.ts +173 -0
- package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +72 -0
- package/telegram-plugin/tests/gateway-bridge.test.ts +811 -0
- package/telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts +414 -0
- package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +144 -0
- package/telegram-plugin/tests/gateway-message-validator.test.ts +133 -0
- package/telegram-plugin/tests/gateway-no-reply-single-emit.test.ts +103 -0
- package/telegram-plugin/tests/gateway-secret-detect.test.ts +127 -0
- package/telegram-plugin/tests/gateway-startup-mutex.test.ts +284 -0
- package/telegram-plugin/tests/gateway-startup-network-retry.test.ts +185 -0
- package/telegram-plugin/tests/gateway-startup-reset.test.ts +72 -0
- package/telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts +125 -0
- package/telegram-plugin/tests/handoff-continuity.test.ts +249 -0
- package/telegram-plugin/tests/harness-ordering-invariants.test.ts +243 -0
- package/telegram-plugin/tests/harness-parse-mode-validation.test.ts +114 -0
- package/telegram-plugin/tests/history.test.ts +364 -0
- package/telegram-plugin/tests/html-balanced.ts +63 -0
- package/telegram-plugin/tests/html-sanitize.test.ts +146 -0
- package/telegram-plugin/tests/idle-footer-wiring.test.ts +88 -0
- package/telegram-plugin/tests/idle-footer.test.ts +66 -0
- package/telegram-plugin/tests/inbound-coalesce.test.ts +127 -0
- package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +150 -0
- package/telegram-plugin/tests/interrupt-marker.test.ts +126 -0
- package/telegram-plugin/tests/ipc-protocol.test.ts +218 -0
- package/telegram-plugin/tests/ipc-server-anonymous-refuse.test.ts +82 -0
- package/telegram-plugin/tests/ipc-server-client.test.ts +323 -0
- package/telegram-plugin/tests/ipc-server-race.test.ts +183 -0
- package/telegram-plugin/tests/ipc-server-validate-operator.test.ts +91 -0
- package/telegram-plugin/tests/ipc-server-validate-pty-partial.test.ts +64 -0
- package/telegram-plugin/tests/ipc-server-validate-update-placeholder.test.ts +77 -0
- package/telegram-plugin/tests/ipc-validator.test.ts +274 -0
- package/telegram-plugin/tests/issues-card.test.ts +495 -0
- package/telegram-plugin/tests/issues-watcher.test.ts +165 -0
- package/telegram-plugin/tests/model-unavailable.test.ts +303 -0
- package/telegram-plugin/tests/multi-turn-continuity.test.ts +159 -0
- package/telegram-plugin/tests/operator-events-history.test.ts +125 -0
- package/telegram-plugin/tests/operator-events-session-tail.test.ts +192 -0
- package/telegram-plugin/tests/operator-events.test.ts +331 -0
- package/telegram-plugin/tests/outbound-ordering.test.ts +293 -0
- package/telegram-plugin/tests/parse-mode-rotation.test.ts +164 -0
- package/telegram-plugin/tests/permission-rule.test.ts +121 -0
- package/telegram-plugin/tests/permission-title.test.ts +106 -0
- package/telegram-plugin/tests/pin-event-log.test.ts +124 -0
- package/telegram-plugin/tests/plugin-logger.test.ts +97 -0
- package/telegram-plugin/tests/poll-health.test.ts +86 -0
- package/telegram-plugin/tests/preamble-suppressor.test.ts +285 -0
- package/telegram-plugin/tests/progress-card-api-failure-during-deferred.test.ts +73 -0
- package/telegram-plugin/tests/progress-card-close-paths-converge.test.ts +272 -0
- package/telegram-plugin/tests/progress-card-cross-turn.test.ts +258 -0
- package/telegram-plugin/tests/progress-card-dispose-preservepending.test.ts +81 -0
- package/telegram-plugin/tests/progress-card-draft-flag.test.ts +80 -0
- package/telegram-plugin/tests/progress-card-driver-eviction.test.ts +215 -0
- package/telegram-plugin/tests/progress-card-driver-fleet-shadow.test.ts +123 -0
- package/telegram-plugin/tests/progress-card-driver-force-complete-parent-done.test.ts +76 -0
- package/telegram-plugin/tests/progress-card-edit-timestamps-budget.test.ts +62 -0
- package/telegram-plugin/tests/progress-card-memory-bounds.test.ts +84 -0
- package/telegram-plugin/tests/progress-card-pin-failure-paths.test.ts +139 -0
- package/telegram-plugin/tests/progress-card-pin-manager.test.ts +773 -0
- package/telegram-plugin/tests/progress-card-pin-race-fast-turn.test.ts +66 -0
- package/telegram-plugin/tests/progress-card-pin-sidecar-partial-write.test.ts +64 -0
- package/telegram-plugin/tests/progress-card-pin-watchdog.test.ts +190 -0
- package/telegram-plugin/tests/progress-card-sigterm-pin-flush.test.ts +146 -0
- package/telegram-plugin/tests/progress-update.test.ts +236 -0
- package/telegram-plugin/tests/protocol-fixtures.test.ts +59 -0
- package/telegram-plugin/tests/protocol-fixtures.ts +198 -0
- package/telegram-plugin/tests/pty-partial-handler.test.ts +326 -0
- package/telegram-plugin/tests/pty-tail-real-fixture.test.ts +114 -0
- package/telegram-plugin/tests/pty-tail-tmux-fragment.test.ts +71 -0
- package/telegram-plugin/tests/pty-tail.test.ts +525 -0
- package/telegram-plugin/tests/quota-cache.test.ts +187 -0
- package/telegram-plugin/tests/quota-check.test.ts +622 -0
- package/telegram-plugin/tests/races.test.ts +842 -0
- package/telegram-plugin/tests/real-gateway-f1-ladder-integrity.test.ts +123 -0
- package/telegram-plugin/tests/real-gateway-f2-instant-draft.test.ts +82 -0
- package/telegram-plugin/tests/real-gateway-f3-late-card.test.ts +114 -0
- package/telegram-plugin/tests/real-gateway-harness.ts +699 -0
- package/telegram-plugin/tests/real-gateway-i6-turn-flush-replay-dedup.test.ts +313 -0
- package/telegram-plugin/tests/real-gateway-ipc-lifecycle.test.ts +299 -0
- package/telegram-plugin/tests/real-gateway-spec.test.ts +487 -0
- package/telegram-plugin/tests/real-gateway.smoke.test.ts +101 -0
- package/telegram-plugin/tests/recent-outbound-dedup.test.ts +192 -0
- package/telegram-plugin/tests/registry-turns.test.ts +432 -0
- package/telegram-plugin/tests/reply-terminal-reaction.test.ts +149 -0
- package/telegram-plugin/tests/resolve-calling-subagent.test.ts +269 -0
- package/telegram-plugin/tests/restart-watchdog.test.ts +224 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +287 -0
- package/telegram-plugin/tests/secret-detect-audit.test.ts +58 -0
- package/telegram-plugin/tests/secret-detect-fail-closed.test.ts +83 -0
- package/telegram-plugin/tests/secret-detect-gitleaks.test.ts +32 -0
- package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +308 -0
- package/telegram-plugin/tests/secret-detect-pipeline.test.ts +123 -0
- package/telegram-plugin/tests/secret-detect-secretlint.test.ts +101 -0
- package/telegram-plugin/tests/secret-detect-staging.test.ts +45 -0
- package/telegram-plugin/tests/secret-detect-suppressor-no-silent-allow.test.ts +67 -0
- package/telegram-plugin/tests/secret-detect.test.ts +223 -0
- package/telegram-plugin/tests/secret-guard-pretool.test.ts +194 -0
- package/telegram-plugin/tests/send-typing-action-validation.test.ts +61 -0
- package/telegram-plugin/tests/session-tail-capped.test.ts +285 -0
- package/telegram-plugin/tests/session-tail.test.ts +524 -0
- package/telegram-plugin/tests/setup-flow.test.ts +510 -0
- package/telegram-plugin/tests/setup-state.test.ts +146 -0
- package/telegram-plugin/tests/silent-reply-guard.test.ts +122 -0
- package/telegram-plugin/tests/slot-banner-driver.e2e.test.ts +350 -0
- package/telegram-plugin/tests/slot-banner.test.ts +74 -0
- package/telegram-plugin/tests/snapshot-serializer.ts +79 -0
- package/telegram-plugin/tests/spawn-detached-cgroup-escape.test.ts +51 -0
- package/telegram-plugin/tests/status-accent.test.ts +186 -0
- package/telegram-plugin/tests/status-reactions-allowed-filter.test.ts +132 -0
- package/telegram-plugin/tests/status-reactions.test.ts +314 -0
- package/telegram-plugin/tests/steering.test.ts +282 -0
- package/telegram-plugin/tests/sticker-aliases.test.ts +232 -0
- package/telegram-plugin/tests/stream-controller-html-fallback.test.ts +127 -0
- package/telegram-plugin/tests/stream-controller.test.ts +262 -0
- package/telegram-plugin/tests/stream-reply-error-paths.test.ts +208 -0
- package/telegram-plugin/tests/stream-reply-handler.test.ts +1292 -0
- package/telegram-plugin/tests/streaming-e2e.test.ts +389 -0
- package/telegram-plugin/tests/streaming-metrics.test.ts +201 -0
- package/telegram-plugin/tests/streaming-orchestration.test.ts +756 -0
- package/telegram-plugin/tests/subagent-registry-bugs.test.ts +725 -0
- package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +213 -0
- package/telegram-plugin/tests/subagent-watcher-parent-marker.test.ts +274 -0
- package/telegram-plugin/tests/subagent-watcher-stall-notification.test.ts +243 -0
- package/telegram-plugin/tests/subagent-watcher.test.ts +877 -0
- package/telegram-plugin/tests/subagents-schema-init-order.test.ts +109 -0
- package/telegram-plugin/tests/sync-chat-running-subagents.test.ts +116 -0
- package/telegram-plugin/tests/telegram-button-constraints.test.ts +194 -0
- package/telegram-plugin/tests/telegram-format.test.ts +1093 -0
- package/telegram-plugin/tests/telegraph.test.ts +246 -0
- package/telegram-plugin/tests/tool-labels.test.ts +383 -0
- package/telegram-plugin/tests/turn-active-marker.test.ts +195 -0
- package/telegram-plugin/tests/turn-end-regressions.test.ts +489 -0
- package/telegram-plugin/tests/turn-flush-card-takeover.test.ts +218 -0
- package/telegram-plugin/tests/turn-flush-dedup-controller.test.ts +144 -0
- package/telegram-plugin/tests/turn-flush-prose-recovery.test.ts +78 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +189 -0
- package/telegram-plugin/tests/turn-signal-tracker.test.ts +107 -0
- package/telegram-plugin/tests/turns-writer.test.ts +323 -0
- package/telegram-plugin/tests/two-zone-bg-carry-full-lifecycle.test.ts +131 -0
- package/telegram-plugin/tests/two-zone-bg-detection.test.ts +120 -0
- package/telegram-plugin/tests/two-zone-bg-done-when-all-terminal.test.ts +114 -0
- package/telegram-plugin/tests/two-zone-bg-early-turn-end.test.ts +87 -0
- package/telegram-plugin/tests/two-zone-bg-survives-next-turn.test.ts +211 -0
- package/telegram-plugin/tests/two-zone-card-cap.test.ts +62 -0
- package/telegram-plugin/tests/two-zone-card-fleet-row.test.ts +101 -0
- package/telegram-plugin/tests/two-zone-card-header-phases.test.ts +68 -0
- package/telegram-plugin/tests/two-zone-card-html-balance.test.ts +110 -0
- package/telegram-plugin/tests/two-zone-card-lifecycle.test.ts +128 -0
- package/telegram-plugin/tests/two-zone-card-sanitise.test.ts +58 -0
- package/telegram-plugin/tests/two-zone-card-snapshot.test.ts +133 -0
- package/telegram-plugin/tests/two-zone-concurrent-turns-isolation.test.ts +155 -0
- package/telegram-plugin/tests/two-zone-phasefor-precedence.test.ts +117 -0
- package/telegram-plugin/tests/two-zone-snapshot-extras.test.ts +143 -0
- package/telegram-plugin/tests/two-zone-stuck-edit-throttle.test.ts +149 -0
- package/telegram-plugin/tests/two-zone-stuck-header-escalation.test.ts +101 -0
- package/telegram-plugin/tests/two-zone-stuck-per-member.test.ts +114 -0
- package/telegram-plugin/tests/two-zone-stuck-recovery.test.ts +105 -0
- package/telegram-plugin/tests/typing-wrap.test.ts +141 -0
- package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +147 -0
- package/telegram-plugin/tests/update-factory-edited-and-reactions.test.ts +108 -0
- package/telegram-plugin/tests/update-factory.ts +305 -0
- package/telegram-plugin/tests/vault-grant-wizard.test.ts +84 -0
- package/telegram-plugin/tests/vault-grants-revoke.test.ts +265 -0
- package/telegram-plugin/tests/vault-subcommands.test.ts +234 -0
- package/telegram-plugin/tests/voice-transcribe.test.ts +196 -0
- package/telegram-plugin/tests/waiting-ux-harness.ts +381 -0
- package/telegram-plugin/tests/waiting-ux.e2e.test.ts +233 -0
- package/telegram-plugin/tests/welcome-text.test.ts +407 -0
- package/telegram-plugin/tool-error-filter.ts +89 -0
- package/telegram-plugin/tool-labels.ts +330 -0
- package/telegram-plugin/tool-names.ts +53 -0
- package/telegram-plugin/turn-flush-prose-recovery.ts +40 -0
- package/telegram-plugin/turn-flush-safety.ts +109 -0
- package/telegram-plugin/turn-signal-tracker.ts +79 -0
- package/telegram-plugin/two-zone-card.ts +249 -0
- package/telegram-plugin/typing-wrap.ts +92 -0
- package/telegram-plugin/voice-transcribe.ts +166 -0
- package/telegram-plugin/welcome-text.ts +359 -0
|
@@ -0,0 +1,1104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/auth` dashboard — pure logic for the inline-keyboard auth surface.
|
|
3
|
+
*
|
|
4
|
+
* When a user sends `/auth` with no args, the gateway renders a mobile-
|
|
5
|
+
* native dashboard: slot list with health badges, utilization bars,
|
|
6
|
+
* and a button grid for the common actions (reauth, add, use, rm,
|
|
7
|
+
* fallback). Tapping a button fires a `callback_query` with a
|
|
8
|
+
* structured `auth:<action>:<agent>[:<slot>]` payload that the gateway
|
|
9
|
+
* routes back to the matching CLI handler.
|
|
10
|
+
*
|
|
11
|
+
* This module holds only the pure parts — dashboard text generator,
|
|
12
|
+
* keyboard builder, and the callback-data parser. Side effects (CLI
|
|
13
|
+
* execs, Telegram API calls) live in gateway.ts so tests run without
|
|
14
|
+
* a bot process or a live filesystem.
|
|
15
|
+
*
|
|
16
|
+
* JTBD rationale:
|
|
17
|
+
* - keep-my-subscription-honest: "user can state in one sentence
|
|
18
|
+
* what they're paying for" — dashboard header lists it in 2 lines
|
|
19
|
+
* (Plan + bank). "When the user hits a plan limit, the product
|
|
20
|
+
* says so honestly" — quota badges + [Fall back] button only
|
|
21
|
+
* visible when hot.
|
|
22
|
+
* - restart-and-know-what-im-running: "auth state is part of the
|
|
23
|
+
* picture" — the dashboard IS the auth picture, tappable.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { InlineKeyboard } from "grammy";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Slot-health values emitted by `switchroom auth list --json`.
|
|
30
|
+
*
|
|
31
|
+
* The CLI distinguishes 'active' (the currently-active slot, which is
|
|
32
|
+
* also healthy) from 'healthy' (a non-active slot with a valid token).
|
|
33
|
+
* Dashboard treats both as healthy for the badge — 'active' is already
|
|
34
|
+
* surfaced via the ● marker and the '(active)' label; duplicating it
|
|
35
|
+
* in the health badge would be noisy.
|
|
36
|
+
*
|
|
37
|
+
* Source: src/auth/accounts.ts SlotHealth enum.
|
|
38
|
+
*/
|
|
39
|
+
export type SlotHealth = "active" | "healthy" | "expired" | "quota-exhausted" | "missing";
|
|
40
|
+
|
|
41
|
+
export interface DashboardSlot {
|
|
42
|
+
slot: string;
|
|
43
|
+
active: boolean;
|
|
44
|
+
health: SlotHealth;
|
|
45
|
+
/** Epoch ms at which the quota window resets (for quota-exhausted). */
|
|
46
|
+
quotaExhaustedUntil?: number | null;
|
|
47
|
+
/** 5-hour utilization as a percentage 0-100, if known. */
|
|
48
|
+
fiveHourPct?: number | null;
|
|
49
|
+
/** 7-day utilization as a percentage 0-100, if known. */
|
|
50
|
+
sevenDayPct?: number | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface DashboardState {
|
|
54
|
+
agent: string;
|
|
55
|
+
bankId: string;
|
|
56
|
+
plan?: string | null;
|
|
57
|
+
/**
|
|
58
|
+
* Anthropic's `rateLimitTier` from the active slot's credentials
|
|
59
|
+
* — e.g. `default_claude_max_5x` vs `default_claude_max_20x`. The
|
|
60
|
+
* tier is the easiest human-visible signal that "the account I
|
|
61
|
+
* meant to authorize with got authorized". Without this, the
|
|
62
|
+
* dashboard just shows `Plan: max` for both tiers and an account
|
|
63
|
+
* mismatch is silent until the agent hits quota.
|
|
64
|
+
*/
|
|
65
|
+
rateLimitTier?: string | null;
|
|
66
|
+
slots: DashboardSlot[];
|
|
67
|
+
/** True when at least one slot shows >= 90% utilization on either
|
|
68
|
+
* window. Toggles the [Fall back now] button's visibility. */
|
|
69
|
+
quotaHot: boolean;
|
|
70
|
+
/** ISO timestamp of the snapshot, shown in the header. */
|
|
71
|
+
generatedAt?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Slot name of the currently-pending auth session, if any.
|
|
74
|
+
*
|
|
75
|
+
* Populated by the gateway from the agent's
|
|
76
|
+
* `.claude/.setup-token.session.json` when present. When non-null,
|
|
77
|
+
* the dashboard renders a `[♻️ Restart flow]` button so the user
|
|
78
|
+
* can explicitly kill + restart the flow if it's gone sideways
|
|
79
|
+
* (browser took too long, claude setup-token crashed, etc.).
|
|
80
|
+
*
|
|
81
|
+
* Complements the automatic stale-session detection in
|
|
82
|
+
* startAuthSession — catches the cases where the user wants to
|
|
83
|
+
* start over BEFORE the challenge actually drifts.
|
|
84
|
+
*/
|
|
85
|
+
pendingSessionSlot?: string | null;
|
|
86
|
+
/**
|
|
87
|
+
* Per-account summaries derived from `switchroom auth account list
|
|
88
|
+
* --json`. Optional: undefined when the gateway can't reach the CLI
|
|
89
|
+
* or the CLI is older than v0.6.x (no --json flag). When present
|
|
90
|
+
* (even as an empty array), the dashboard renders the accounts
|
|
91
|
+
* section. The `enabledHere` flag drives the ✓/○ marker — `agents`
|
|
92
|
+
* field from the JSON, with `agents.includes(state.agent)` mapped
|
|
93
|
+
* into this struct by the gateway.
|
|
94
|
+
*/
|
|
95
|
+
accounts?: ReadonlyArray<AccountSummary>;
|
|
96
|
+
/** True when more accounts exist than `ACCOUNTS_DISPLAY_CAP` — the
|
|
97
|
+
* render appends a noop "more accounts (use CLI)" row. */
|
|
98
|
+
accountsTruncated?: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* True when this agent has slot credentials we could promote into a
|
|
101
|
+
* shared account via `auth share`. Drives the bootstrap "🌐 Share to
|
|
102
|
+
* fleet" button visibility — only useful when no accounts exist yet.
|
|
103
|
+
*/
|
|
104
|
+
canBootstrapShare?: boolean;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Per-account summary for the inline-keyboard dashboard's accounts
|
|
109
|
+
* section. Mirrors the JSON shape `auth account list --json` emits,
|
|
110
|
+
* collapsed to the fields the renderer needs. Pure data — no behaviour.
|
|
111
|
+
*/
|
|
112
|
+
export type AccountHealth =
|
|
113
|
+
| "healthy"
|
|
114
|
+
| "quota-exhausted"
|
|
115
|
+
| "expired"
|
|
116
|
+
| "missing-credentials"
|
|
117
|
+
| "missing-refresh-token";
|
|
118
|
+
|
|
119
|
+
export interface AccountSummary {
|
|
120
|
+
readonly label: string;
|
|
121
|
+
readonly health: AccountHealth;
|
|
122
|
+
/** True when this agent appears in the account's `agents` list. */
|
|
123
|
+
readonly enabledHere: boolean;
|
|
124
|
+
readonly subscriptionType?: string;
|
|
125
|
+
readonly expiresAt?: number;
|
|
126
|
+
/**
|
|
127
|
+
* Per-account 5h-window utilization, 0–100. Populated by the
|
|
128
|
+
* gateway's account-level quota probe — mirrored from the
|
|
129
|
+
* `anthropic-ratelimit-unified-5h-utilization` header on the
|
|
130
|
+
* Anthropic API response. Undefined means "not probed yet" (the
|
|
131
|
+
* dashboard renders a placeholder rather than 0%).
|
|
132
|
+
*/
|
|
133
|
+
readonly fiveHourPct?: number;
|
|
134
|
+
/**
|
|
135
|
+
* Per-account 7d-window utilization. Same source as
|
|
136
|
+
* {@link fiveHourPct} — `anthropic-ratelimit-unified-7d-utilization`.
|
|
137
|
+
*/
|
|
138
|
+
readonly sevenDayPct?: number;
|
|
139
|
+
/** Unix ms when the 5h cap resets, when known. */
|
|
140
|
+
readonly fiveHourResetAt?: number;
|
|
141
|
+
/** Unix ms when the 7d cap resets, when known. */
|
|
142
|
+
readonly sevenDayResetAt?: number;
|
|
143
|
+
/**
|
|
144
|
+
* Unix ms when the account is expected to come back from a
|
|
145
|
+
* quota-exhausted state. Populated when the cached probe says the
|
|
146
|
+
* account is exhausted (server-side `quota-exhausted` or local
|
|
147
|
+
* 5h utilization == 100%). Render shows "exhausted · resets in
|
|
148
|
+
* Nh Mm" rather than the percentage row.
|
|
149
|
+
*/
|
|
150
|
+
readonly quotaExhaustedUntil?: number;
|
|
151
|
+
/**
|
|
152
|
+
* True when this account sits at index 0 of THIS agent's
|
|
153
|
+
* `auth.accounts:` list — i.e. it's the post-fanout active for this
|
|
154
|
+
* agent. Drives the `▶` glyph + "Active" framing in the dashboard
|
|
155
|
+
* render and suppresses the per-account `⤴ Promote` button (you
|
|
156
|
+
* can't promote what's already primary).
|
|
157
|
+
*
|
|
158
|
+
* Populated by the gateway from the new `primaryForAgents` field on
|
|
159
|
+
* `auth account list --json` (added v0.6.9). Optional: undefined
|
|
160
|
+
* means "old CLI without the field" — render falls back to the
|
|
161
|
+
* pre-v3 unmarked layout.
|
|
162
|
+
*/
|
|
163
|
+
readonly activeForThisAgent?: boolean;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Thresholds that govern what counts as "quota hot" — the boundary at
|
|
168
|
+
* which we surface the [Fall back now] button without the user asking.
|
|
169
|
+
* Aligned with the auto-fallback poller's trigger point in
|
|
170
|
+
* telegram-plugin/auto-fallback.ts (DEFAULT_TRIGGER_UTILIZATION_PCT
|
|
171
|
+
* = 99.5) but relaxed a little for the "you might want to act" UX
|
|
172
|
+
* affordance on the dashboard — the button appearing at 90% gives the
|
|
173
|
+
* user agency before the automatic fallback takes over.
|
|
174
|
+
*/
|
|
175
|
+
export const QUOTA_HOT_THRESHOLD_PCT = 90;
|
|
176
|
+
|
|
177
|
+
/** Max account rows rendered inline. Beyond this, the dashboard adds a
|
|
178
|
+
* truncated-noop row pointing the user to the CLI for the rest. Five
|
|
179
|
+
* is enough for typical fleets without overflowing a mobile screen. */
|
|
180
|
+
export const ACCOUNTS_DISPLAY_CAP = 5;
|
|
181
|
+
|
|
182
|
+
/** Telegram caps callback_data at 64 bytes. Render-time guard rejects
|
|
183
|
+
* encoded payloads beyond this and renders a noop fallback button. */
|
|
184
|
+
export const CALLBACK_BUDGET_BYTES = 64;
|
|
185
|
+
|
|
186
|
+
export type CallbackAction =
|
|
187
|
+
| { kind: "refresh"; agent: string }
|
|
188
|
+
| { kind: "reauth"; agent: string; slot?: string }
|
|
189
|
+
| { kind: "add"; agent: string }
|
|
190
|
+
| { kind: "use"; agent: string; slot: string }
|
|
191
|
+
| { kind: "rm"; agent: string; slot: string }
|
|
192
|
+
| { kind: "confirm-rm"; agent: string; slot: string }
|
|
193
|
+
| { kind: "fallback"; agent: string }
|
|
194
|
+
| { kind: "usage"; agent: string }
|
|
195
|
+
| { kind: "restart-flow"; agent: string; slot: string }
|
|
196
|
+
// Account-level (#per-agent-cards / #share-auth-across-the-fleet).
|
|
197
|
+
// Single-character verbs (ae/ad/cae/cad/sf) maximise label headroom
|
|
198
|
+
// inside the 64-byte callback_data cap.
|
|
199
|
+
| { kind: "account-enable"; agent: string; label: string }
|
|
200
|
+
| { kind: "account-disable"; agent: string; label: string }
|
|
201
|
+
| { kind: "confirm-account-enable"; agent: string; label: string }
|
|
202
|
+
| { kind: "confirm-account-disable"; agent: string; label: string }
|
|
203
|
+
| { kind: "share-fleet"; agent: string }
|
|
204
|
+
// v3a: per-account drill-down sub-view (accounts-first redesign).
|
|
205
|
+
// Short verbs (av/arm/armc/ara) preserve label headroom in 64-byte cap.
|
|
206
|
+
| { kind: "account-view"; agent: string; label: string }
|
|
207
|
+
| { kind: "account-rm"; agent: string; label: string }
|
|
208
|
+
| { kind: "account-rm-confirm"; agent: string; label: string }
|
|
209
|
+
| { kind: "account-reauth"; agent: string; label: string }
|
|
210
|
+
// v3b: in-place promote — moves a fallback to primary without leaving
|
|
211
|
+
// the dashboard. Two-stage confirm mirrors enable/disable. Verbs `apr`
|
|
212
|
+
// / `cpr` are 3 chars max so a 40-char agent + 64-char label still
|
|
213
|
+
// fits the 64-byte callback_data cap (auth:cpr:agent:label = 12 +
|
|
214
|
+
// agent + label ≤ 64).
|
|
215
|
+
| { kind: "account-promote"; agent: string; label: string }
|
|
216
|
+
| { kind: "confirm-account-promote"; agent: string; label: string }
|
|
217
|
+
// v3c: switch-primary picker. Replaces the per-fallback `⤴ Promote`
|
|
218
|
+
// buttons that flooded the main board with a single `🔀 Switch
|
|
219
|
+
// primary →` button. Tapping it edits the keyboard in-place to a
|
|
220
|
+
// picker view (one row per fallback → tap → confirm-account-promote).
|
|
221
|
+
// Cancel returns to the main dashboard via a refresh.
|
|
222
|
+
| { kind: "switch-primary-view"; agent: string }
|
|
223
|
+
| { kind: "noop" };
|
|
224
|
+
|
|
225
|
+
const CALLBACK_PREFIX = "auth:";
|
|
226
|
+
|
|
227
|
+
/** Encode an action into the <=64-byte callback_data string Telegram
|
|
228
|
+
* allows. Keep the shape `auth:<verb>:<agent>[:<slot>]` — single-level
|
|
229
|
+
* parser, no JSON, no escaping headaches. */
|
|
230
|
+
export function encodeCallbackData(action: CallbackAction): string {
|
|
231
|
+
switch (action.kind) {
|
|
232
|
+
case "refresh":
|
|
233
|
+
return `${CALLBACK_PREFIX}refresh:${action.agent}`;
|
|
234
|
+
case "reauth":
|
|
235
|
+
return action.slot
|
|
236
|
+
? `${CALLBACK_PREFIX}reauth:${action.agent}:${action.slot}`
|
|
237
|
+
: `${CALLBACK_PREFIX}reauth:${action.agent}`;
|
|
238
|
+
case "add":
|
|
239
|
+
return `${CALLBACK_PREFIX}add:${action.agent}`;
|
|
240
|
+
case "use":
|
|
241
|
+
return `${CALLBACK_PREFIX}use:${action.agent}:${action.slot}`;
|
|
242
|
+
case "rm":
|
|
243
|
+
return `${CALLBACK_PREFIX}rm:${action.agent}:${action.slot}`;
|
|
244
|
+
case "confirm-rm":
|
|
245
|
+
return `${CALLBACK_PREFIX}confirm-rm:${action.agent}:${action.slot}`;
|
|
246
|
+
case "fallback":
|
|
247
|
+
return `${CALLBACK_PREFIX}fallback:${action.agent}`;
|
|
248
|
+
case "usage":
|
|
249
|
+
return `${CALLBACK_PREFIX}usage:${action.agent}`;
|
|
250
|
+
case "restart-flow":
|
|
251
|
+
return `${CALLBACK_PREFIX}restart-flow:${action.agent}:${action.slot}`;
|
|
252
|
+
case "account-enable":
|
|
253
|
+
return `${CALLBACK_PREFIX}ae:${action.agent}:${action.label}`;
|
|
254
|
+
case "account-disable":
|
|
255
|
+
return `${CALLBACK_PREFIX}ad:${action.agent}:${action.label}`;
|
|
256
|
+
case "confirm-account-enable":
|
|
257
|
+
return `${CALLBACK_PREFIX}cae:${action.agent}:${action.label}`;
|
|
258
|
+
case "confirm-account-disable":
|
|
259
|
+
return `${CALLBACK_PREFIX}cad:${action.agent}:${action.label}`;
|
|
260
|
+
case "share-fleet":
|
|
261
|
+
return `${CALLBACK_PREFIX}sf:${action.agent}`;
|
|
262
|
+
case "account-view":
|
|
263
|
+
return `${CALLBACK_PREFIX}av:${action.agent}:${action.label}`;
|
|
264
|
+
case "account-rm":
|
|
265
|
+
return `${CALLBACK_PREFIX}arm:${action.agent}:${action.label}`;
|
|
266
|
+
case "account-rm-confirm":
|
|
267
|
+
return `${CALLBACK_PREFIX}armc:${action.agent}:${action.label}`;
|
|
268
|
+
case "account-reauth":
|
|
269
|
+
return `${CALLBACK_PREFIX}ara:${action.agent}:${action.label}`;
|
|
270
|
+
case "account-promote":
|
|
271
|
+
return `${CALLBACK_PREFIX}apr:${action.agent}:${action.label}`;
|
|
272
|
+
case "confirm-account-promote":
|
|
273
|
+
return `${CALLBACK_PREFIX}cpr:${action.agent}:${action.label}`;
|
|
274
|
+
case "switch-primary-view":
|
|
275
|
+
return `${CALLBACK_PREFIX}spv:${action.agent}`;
|
|
276
|
+
case "noop":
|
|
277
|
+
return `${CALLBACK_PREFIX}noop`;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Parse the gateway's inbound callback_data into an action. Returns
|
|
282
|
+
* `{kind: 'noop'}` for anything that doesn't match our shape — the
|
|
283
|
+
* caller should still answerCallbackQuery() but otherwise drop. */
|
|
284
|
+
export function parseCallbackData(data: string): CallbackAction {
|
|
285
|
+
if (!data.startsWith(CALLBACK_PREFIX)) return { kind: "noop" };
|
|
286
|
+
// Reject payloads beyond Telegram's 64-byte cap. Telegram itself
|
|
287
|
+
// refuses to deliver those, but the parser stays defensive in case
|
|
288
|
+
// a test or fuzzer hands us one.
|
|
289
|
+
if (Buffer.byteLength(data, "utf8") > CALLBACK_BUDGET_BYTES) {
|
|
290
|
+
return { kind: "noop" };
|
|
291
|
+
}
|
|
292
|
+
const rest = data.slice(CALLBACK_PREFIX.length);
|
|
293
|
+
const parts = rest.split(":");
|
|
294
|
+
const [verb, agent, third] = parts;
|
|
295
|
+
// Account-level verbs (single-char) accept a label as the third
|
|
296
|
+
// segment instead of a slot. We branch on verb first so each segment
|
|
297
|
+
// is validated against its own regex.
|
|
298
|
+
if (verb === "ae" || verb === "ad" || verb === "cae" || verb === "cad" ||
|
|
299
|
+
verb === "av" || verb === "arm" || verb === "armc" || verb === "ara" ||
|
|
300
|
+
verb === "apr" || verb === "cpr") {
|
|
301
|
+
if (!isSafeAgentName(agent ?? "")) return { kind: "noop" };
|
|
302
|
+
if (!third || !isSafeAccountLabel(third)) return { kind: "noop" };
|
|
303
|
+
const label = third;
|
|
304
|
+
if (verb === "ae") return { kind: "account-enable", agent, label };
|
|
305
|
+
if (verb === "ad") return { kind: "account-disable", agent, label };
|
|
306
|
+
if (verb === "cae") return { kind: "confirm-account-enable", agent, label };
|
|
307
|
+
if (verb === "cad") return { kind: "confirm-account-disable", agent, label };
|
|
308
|
+
if (verb === "av") return { kind: "account-view", agent, label };
|
|
309
|
+
if (verb === "arm") return { kind: "account-rm", agent, label };
|
|
310
|
+
if (verb === "armc") return { kind: "account-rm-confirm", agent, label };
|
|
311
|
+
if (verb === "ara") return { kind: "account-reauth", agent, label };
|
|
312
|
+
if (verb === "apr") return { kind: "account-promote", agent, label };
|
|
313
|
+
// verb === "cpr"
|
|
314
|
+
return { kind: "confirm-account-promote", agent, label };
|
|
315
|
+
}
|
|
316
|
+
if (verb === "sf") {
|
|
317
|
+
if (!isSafeAgentName(agent ?? "")) return { kind: "noop" };
|
|
318
|
+
return { kind: "share-fleet", agent };
|
|
319
|
+
}
|
|
320
|
+
if (verb === "spv") {
|
|
321
|
+
if (!isSafeAgentName(agent ?? "")) return { kind: "noop" };
|
|
322
|
+
return { kind: "switch-primary-view", agent };
|
|
323
|
+
}
|
|
324
|
+
if (!isSafeAgentName(agent ?? "")) return { kind: "noop" };
|
|
325
|
+
const slot = third;
|
|
326
|
+
switch (verb) {
|
|
327
|
+
case "refresh":
|
|
328
|
+
return { kind: "refresh", agent };
|
|
329
|
+
case "reauth":
|
|
330
|
+
return slot && isSafeSlotName(slot)
|
|
331
|
+
? { kind: "reauth", agent, slot }
|
|
332
|
+
: { kind: "reauth", agent };
|
|
333
|
+
case "add":
|
|
334
|
+
return { kind: "add", agent };
|
|
335
|
+
case "use":
|
|
336
|
+
if (!slot || !isSafeSlotName(slot)) return { kind: "noop" };
|
|
337
|
+
return { kind: "use", agent, slot };
|
|
338
|
+
case "rm":
|
|
339
|
+
if (!slot || !isSafeSlotName(slot)) return { kind: "noop" };
|
|
340
|
+
return { kind: "rm", agent, slot };
|
|
341
|
+
case "confirm-rm":
|
|
342
|
+
if (!slot || !isSafeSlotName(slot)) return { kind: "noop" };
|
|
343
|
+
return { kind: "confirm-rm", agent, slot };
|
|
344
|
+
case "fallback":
|
|
345
|
+
return { kind: "fallback", agent };
|
|
346
|
+
case "usage":
|
|
347
|
+
return { kind: "usage", agent };
|
|
348
|
+
case "restart-flow":
|
|
349
|
+
if (!slot || !isSafeSlotName(slot)) return { kind: "noop" };
|
|
350
|
+
return { kind: "restart-flow", agent, slot };
|
|
351
|
+
default:
|
|
352
|
+
return { kind: "noop" };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function isSafeAgentName(name: string): boolean {
|
|
357
|
+
return /^[a-zA-Z0-9_-]{1,64}$/.test(name);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function isSafeSlotName(name: string): boolean {
|
|
361
|
+
return /^[a-zA-Z0-9_-]{1,32}$/.test(name);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Account labels match the CLI's `validateAccountLabel` regex
|
|
366
|
+
* (`src/auth/account-store.ts`): `[A-Za-z0-9._@+-]{1,64}`. The label
|
|
367
|
+
* accepts email-shaped strings (`pixsoul@gmail.com`) and gmail-tag
|
|
368
|
+
* forms (`ken+work@example.com`) so operators can label accounts by
|
|
369
|
+
* the Anthropic email they signed up with — the JTBD's "the user
|
|
370
|
+
* manages accounts" works best when the labels read like the
|
|
371
|
+
* identities the user already knows.
|
|
372
|
+
*
|
|
373
|
+
* Dashboard-side validator so the parser doesn't need to import
|
|
374
|
+
* from `src/`. Keep in sync with `LABEL_RE` in account-store.ts and
|
|
375
|
+
* `ACCOUNT_LABEL_RE` in auth-slot-parser.ts.
|
|
376
|
+
*
|
|
377
|
+
* The `.` and `..` reservations match the CLI's defensive guards —
|
|
378
|
+
* those tokens are valid characters but reserved as filesystem
|
|
379
|
+
* lookalikes that would create ambiguous on-disk paths under
|
|
380
|
+
* `~/.switchroom/accounts/`. `:` is omitted on purpose because it
|
|
381
|
+
* would corrupt callback_data parsing in the Telegram dashboard.
|
|
382
|
+
*/
|
|
383
|
+
export function isSafeAccountLabel(name: string): boolean {
|
|
384
|
+
if (name === "." || name === "..") return false;
|
|
385
|
+
return /^[A-Za-z0-9._@+-]{1,64}$/.test(name);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Build the dashboard message text + inline keyboard. Pure — no side
|
|
390
|
+
* effects. The gateway sends the result via ctx.reply or editMessageText.
|
|
391
|
+
*/
|
|
392
|
+
export function buildDashboard(state: DashboardState): {
|
|
393
|
+
text: string;
|
|
394
|
+
keyboard: InlineKeyboard;
|
|
395
|
+
} {
|
|
396
|
+
return {
|
|
397
|
+
text: buildDashboardText(state),
|
|
398
|
+
keyboard: buildDashboardKeyboard(state),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function buildDashboardText(state: DashboardState): string {
|
|
403
|
+
const lines: string[] = [];
|
|
404
|
+
lines.push(`━━━ <b>Auth • ${escapeHtml(state.agent)}</b> ━━━`);
|
|
405
|
+
// Show the full rate-limit tier when we have it — e.g. 'max_5x' vs
|
|
406
|
+
// 'max_20x' lets the user tell at a glance whether the correct
|
|
407
|
+
// Anthropic account got authorized during reauth. Otherwise fall
|
|
408
|
+
// back to the plain plan name.
|
|
409
|
+
const tierLabel = state.rateLimitTier
|
|
410
|
+
? formatRateLimitTier(state.rateLimitTier)
|
|
411
|
+
: state.plan
|
|
412
|
+
? state.plan
|
|
413
|
+
: null;
|
|
414
|
+
const planLine = tierLabel
|
|
415
|
+
? `Bank: <code>${escapeHtml(state.bankId)}</code> · Plan: <b>${escapeHtml(tierLabel)}</b>`
|
|
416
|
+
: `Bank: <code>${escapeHtml(state.bankId)}</code>`;
|
|
417
|
+
lines.push(planLine);
|
|
418
|
+
lines.push("");
|
|
419
|
+
|
|
420
|
+
// v3a: accounts appear above slots — accounts are first-class, slots
|
|
421
|
+
// are an implementation detail of how credentials attach to a process.
|
|
422
|
+
// v3b: active account (the one at this agent's auth.accounts[0])
|
|
423
|
+
// floats to the top with a `▶` glyph; remaining rows render under a
|
|
424
|
+
// "Fallback:" subhead in agent-list order. When `activeForThisAgent`
|
|
425
|
+
// is unset on every entry (older CLI without primaryForAgents in
|
|
426
|
+
// --json), we fall back to the v3a layout — bullets only, no header.
|
|
427
|
+
if (state.accounts != null && state.accounts.length > 0) {
|
|
428
|
+
lines.push(`<b>Anthropic accounts (${state.accounts.length})</b>`);
|
|
429
|
+
const visible = state.accounts.slice(0, ACCOUNTS_DISPLAY_CAP);
|
|
430
|
+
const active = visible.find((a) => a.activeForThisAgent === true);
|
|
431
|
+
const fallbacks = visible.filter((a) => a !== active);
|
|
432
|
+
const haveActiveSignal = active != null;
|
|
433
|
+
if (haveActiveSignal && active != null) {
|
|
434
|
+
lines.push(renderActiveAccountRow(active));
|
|
435
|
+
}
|
|
436
|
+
if (fallbacks.length > 0) {
|
|
437
|
+
// Only emit the subhead when there's a distinguished active row;
|
|
438
|
+
// otherwise the list is just "all accounts, no opinion" and a
|
|
439
|
+
// header would be misleading.
|
|
440
|
+
if (haveActiveSignal) lines.push(` <i>Fallback ↓:</i>`);
|
|
441
|
+
for (const acc of fallbacks) {
|
|
442
|
+
lines.push(renderFallbackAccountRow(acc, haveActiveSignal));
|
|
443
|
+
const quotaLine = formatAccountQuotaLine(acc);
|
|
444
|
+
if (quotaLine) lines.push(` └ ${quotaLine}`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (state.accountsTruncated) {
|
|
448
|
+
lines.push(` … ${state.accounts.length - ACCOUNTS_DISPLAY_CAP} more (use CLI)`);
|
|
449
|
+
}
|
|
450
|
+
lines.push("");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Slot ID lookup: under the new account model, slot IDs (`default`,
|
|
454
|
+
// etc.) are an internal mount-point identifier — not what the
|
|
455
|
+
// operator authorized. When we know which account is the post-fanout
|
|
456
|
+
// active for THIS agent, the active slot row would render as
|
|
457
|
+
// `● pixsoul@gmail.com (active) ✓ healthy` and the Pool line would
|
|
458
|
+
// say `Pool: pixsoul@gmail.com is active` — both 1:1 duplicates of
|
|
459
|
+
// the ▶ active-account row above. So we hide both sections when an
|
|
460
|
+
// active-account signal is present. Keep them visible only when:
|
|
461
|
+
// - No accounts data at all (older CLI without --json), OR
|
|
462
|
+
// - Accounts exist but no entry has activeForThisAgent set (older
|
|
463
|
+
// CLI without primaryForAgents), OR
|
|
464
|
+
// - Empty fleet (no accounts) — slots are still the bootstrap
|
|
465
|
+
// surface for the operator's first reauth/add taps.
|
|
466
|
+
const activeAccountLabel =
|
|
467
|
+
state.accounts?.find((a) => a.activeForThisAgent === true)?.label ?? null;
|
|
468
|
+
const slotsSectionRedundant =
|
|
469
|
+
activeAccountLabel != null &&
|
|
470
|
+
state.accounts != null &&
|
|
471
|
+
state.accounts.length > 0;
|
|
472
|
+
|
|
473
|
+
if (!slotsSectionRedundant) {
|
|
474
|
+
if (state.slots.length === 0) {
|
|
475
|
+
lines.push("<i>No account slots. Tap [➕ Add slot] to attach a subscription.</i>");
|
|
476
|
+
} else {
|
|
477
|
+
lines.push(`<b>Slots (${state.slots.length})</b>`);
|
|
478
|
+
for (const slot of state.slots) {
|
|
479
|
+
const marker = slot.active ? "●" : "○";
|
|
480
|
+
const badge = healthBadge(slot.health);
|
|
481
|
+
const label = healthLabel(slot.health);
|
|
482
|
+
lines.push(
|
|
483
|
+
` ${marker} <code>${escapeHtml(slot.slot)}</code>${slot.active ? " (active)" : ""} ${badge} ${label}`,
|
|
484
|
+
);
|
|
485
|
+
const detail = slotDetailLine(slot);
|
|
486
|
+
if (detail) lines.push(` └ ${detail}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Pool / fallback summary — show when accounts exist, so the user
|
|
491
|
+
// understands how slots and accounts relate. Suppressed alongside
|
|
492
|
+
// the slots section when the active-account row already says it.
|
|
493
|
+
if (state.accounts != null && state.accounts.length > 0 && state.slots.length > 0) {
|
|
494
|
+
const activeSlot = state.slots.find((s) => s.active);
|
|
495
|
+
if (activeSlot) {
|
|
496
|
+
lines.push(` Pool: slot <code>${escapeHtml(activeSlot.slot)}</code> is active`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
lines.push("");
|
|
502
|
+
if (state.pendingSessionSlot) {
|
|
503
|
+
lines.push(
|
|
504
|
+
`<i>⏳ Auth flow pending for slot <code>${escapeHtml(state.pendingSessionSlot)}</code>. If it's stuck, tap ♻️ below to restart.</i>`,
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
lines.push("━━━━━━━━━━━━━━━━━━━");
|
|
508
|
+
if (state.generatedAt) {
|
|
509
|
+
lines.push(`<i>Updated ${escapeHtml(state.generatedAt)}</i>`);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return lines.join("\n");
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Render the active account row — the post-fanout primary for this
|
|
517
|
+
* agent. Uses the `▶` glyph + bold label + an inline quota summary
|
|
518
|
+
* carrying mini-bars when both percentages are known. Falls back to
|
|
519
|
+
* the plain `formatAccountQuotaLine` text on the next line if quota
|
|
520
|
+
* isn't probed yet — keeps the row honest about uncertainty.
|
|
521
|
+
*/
|
|
522
|
+
function renderActiveAccountRow(acc: AccountSummary): string {
|
|
523
|
+
const badge = accountHealthBadge(acc.health);
|
|
524
|
+
const suffix = healthSuffix(acc.health);
|
|
525
|
+
const head = `▶ <b><code>${escapeHtml(acc.label)}</code></b> ${badge}${suffix}`;
|
|
526
|
+
const inline = formatActiveQuotaInline(acc);
|
|
527
|
+
return inline ? `${head}\n ${inline}` : head;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Render an indented fallback account row. `haveActiveSignal` controls
|
|
532
|
+
* the bullet vs. tree-prefix character — when there's a distinguished
|
|
533
|
+
* active row above, we use `↳` to imply ordering; without one we fall
|
|
534
|
+
* back to a plain `•` bullet so the layout matches v3a for older CLIs.
|
|
535
|
+
*/
|
|
536
|
+
function renderFallbackAccountRow(
|
|
537
|
+
acc: AccountSummary,
|
|
538
|
+
haveActiveSignal: boolean,
|
|
539
|
+
): string {
|
|
540
|
+
const badge = accountHealthBadge(acc.health);
|
|
541
|
+
const suffix = healthSuffix(acc.health);
|
|
542
|
+
const prefix = haveActiveSignal ? " ↳" : " •";
|
|
543
|
+
return `${prefix} <code>${escapeHtml(acc.label)}</code> ${badge}${suffix}`;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Inline quota summary for the active row. When BOTH 5h and 7d are
|
|
548
|
+
* known, emit the mini-bar form (`5h ████░░ 47% · 7d █░░░░░ 12%`).
|
|
549
|
+
* When the account is exhausted, defer to the existing
|
|
550
|
+
* `formatAccountQuotaLine` (it has the reset-time copy). Otherwise
|
|
551
|
+
* return null and let the caller skip the line.
|
|
552
|
+
*/
|
|
553
|
+
function formatActiveQuotaInline(acc: AccountSummary): string | null {
|
|
554
|
+
if (acc.quotaExhaustedUntil != null && acc.quotaExhaustedUntil > Date.now()) {
|
|
555
|
+
return formatAccountQuotaLine(acc);
|
|
556
|
+
}
|
|
557
|
+
if (acc.fiveHourPct == null || acc.sevenDayPct == null) {
|
|
558
|
+
return formatAccountQuotaLine(acc);
|
|
559
|
+
}
|
|
560
|
+
const fiveBar = formatQuotaBar(acc.fiveHourPct);
|
|
561
|
+
const sevenBar = formatQuotaBar(acc.sevenDayPct);
|
|
562
|
+
return (
|
|
563
|
+
`<i>5h</i> <code>${fiveBar}</code> ${formatQuotaPct(acc.fiveHourPct)} ` +
|
|
564
|
+
`· <i>7d</i> <code>${sevenBar}</code> ${formatQuotaPct(acc.sevenDayPct)}`
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/** Health badge for an account (not a slot). */
|
|
569
|
+
function accountHealthBadge(health: AccountHealth): string {
|
|
570
|
+
switch (health) {
|
|
571
|
+
case "healthy":
|
|
572
|
+
return "✓";
|
|
573
|
+
case "quota-exhausted":
|
|
574
|
+
return "⚠️";
|
|
575
|
+
case "expired":
|
|
576
|
+
case "missing-refresh-token":
|
|
577
|
+
return "⌛";
|
|
578
|
+
case "missing-credentials":
|
|
579
|
+
return "✗";
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function slotDetailLine(slot: DashboardSlot): string | null {
|
|
584
|
+
const bits: string[] = [];
|
|
585
|
+
if (slot.fiveHourPct != null) bits.push(`5h: ${Math.round(slot.fiveHourPct)}%`);
|
|
586
|
+
if (slot.sevenDayPct != null) bits.push(`7d: ${Math.round(slot.sevenDayPct)}%`);
|
|
587
|
+
if (slot.health === "quota-exhausted" && slot.quotaExhaustedUntil) {
|
|
588
|
+
const mins = Math.max(0, Math.round((slot.quotaExhaustedUntil - Date.now()) / 60_000));
|
|
589
|
+
bits.push(`resets in ~${mins}m`);
|
|
590
|
+
} else if (slot.health === "expired") {
|
|
591
|
+
bits.push("run reauth");
|
|
592
|
+
}
|
|
593
|
+
return bits.length > 0 ? bits.join(" · ") : null;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function healthBadge(health: SlotHealth): string {
|
|
597
|
+
switch (health) {
|
|
598
|
+
case "active":
|
|
599
|
+
case "healthy":
|
|
600
|
+
return "✓";
|
|
601
|
+
case "quota-exhausted":
|
|
602
|
+
return "⚠️";
|
|
603
|
+
case "expired":
|
|
604
|
+
return "⌛";
|
|
605
|
+
case "missing":
|
|
606
|
+
return "✗";
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Human-readable label for a slot's health. 'active' collapses to
|
|
612
|
+
* 'healthy' — the ● + '(active)' markers already carry the active-slot
|
|
613
|
+
* signal; rendering 'active active' is redundant.
|
|
614
|
+
*/
|
|
615
|
+
function healthLabel(health: SlotHealth): string {
|
|
616
|
+
return health === "active" ? "healthy" : health;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export function buildDashboardKeyboard(state: DashboardState): InlineKeyboard {
|
|
620
|
+
const kb = new InlineKeyboard();
|
|
621
|
+
const activeSlot = state.slots.find((s) => s.active);
|
|
622
|
+
|
|
623
|
+
// v3c: single `🔀 Switch primary →` entry replaces the v3b
|
|
624
|
+
// per-fallback `⤴ Promote` buttons + per-account drill-downs that
|
|
625
|
+
// flooded the main board. The text already names every account
|
|
626
|
+
// (`▶ active` + indented `↳ fallback` rows), so the keyboard's job
|
|
627
|
+
// is *actions*, not navigation. One button, one tap → picker.
|
|
628
|
+
//
|
|
629
|
+
// Visibility rules:
|
|
630
|
+
// - hidden when there are no fallbacks (single account = nothing
|
|
631
|
+
// to switch to)
|
|
632
|
+
// - hidden when no account claims active (older CLI without
|
|
633
|
+
// primaryForAgents — picker target would be ambiguous)
|
|
634
|
+
// - shown otherwise
|
|
635
|
+
if (state.accounts != null && state.accounts.length > 0) {
|
|
636
|
+
const visible = state.accounts.slice(0, ACCOUNTS_DISPLAY_CAP);
|
|
637
|
+
const active = visible.find((a) => a.activeForThisAgent === true);
|
|
638
|
+
const fallbacks = visible.filter((a) => a !== active);
|
|
639
|
+
if (active != null && fallbacks.length > 0) {
|
|
640
|
+
kb.text(
|
|
641
|
+
"🔀 Switch primary →",
|
|
642
|
+
encodeCallbackData({ kind: "switch-primary-view", agent: state.agent }),
|
|
643
|
+
);
|
|
644
|
+
kb.row();
|
|
645
|
+
}
|
|
646
|
+
if (state.accountsTruncated) {
|
|
647
|
+
kb.text(
|
|
648
|
+
`… ${state.accounts.length - ACCOUNTS_DISPLAY_CAP} more (use CLI)`,
|
|
649
|
+
encodeCallbackData({ kind: "noop" }),
|
|
650
|
+
);
|
|
651
|
+
kb.row();
|
|
652
|
+
}
|
|
653
|
+
} else if (state.accounts != null && state.accounts.length === 0 && state.canBootstrapShare) {
|
|
654
|
+
// Bootstrap one-tap: zero accounts exist, but this agent has
|
|
655
|
+
// healthy slot creds we could promote. Synthesises label="default"
|
|
656
|
+
// at the gateway so the user gets a reasonable starting state in
|
|
657
|
+
// one tap; rename via CLI later if "default" doesn't suit.
|
|
658
|
+
kb.text(
|
|
659
|
+
"🌐 Share to fleet",
|
|
660
|
+
encodeCallbackData({ kind: "share-fleet", agent: state.agent }),
|
|
661
|
+
);
|
|
662
|
+
kb.row();
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Slot rows — existing Reauth/Add/Use/Remove behavior, unchanged.
|
|
666
|
+
// Slots are still real and operators still need to manage them;
|
|
667
|
+
// they're just demoted below accounts in the v3a layout.
|
|
668
|
+
|
|
669
|
+
// Slot row A: primary auth actions. Reauth the active slot; add a new one.
|
|
670
|
+
if (activeSlot) {
|
|
671
|
+
kb.text(`🔄 Reauth ${activeSlot.slot}`, encodeCallbackData({ kind: "reauth", agent: state.agent, slot: activeSlot.slot }));
|
|
672
|
+
} else {
|
|
673
|
+
kb.text("🔄 Reauth", encodeCallbackData({ kind: "reauth", agent: state.agent }));
|
|
674
|
+
}
|
|
675
|
+
kb.text("➕ Add slot", encodeCallbackData({ kind: "add", agent: state.agent }));
|
|
676
|
+
kb.row();
|
|
677
|
+
|
|
678
|
+
// Slot row B: non-active slots — one "Use" button per, up to 3 to
|
|
679
|
+
// avoid runaway rows. Over 3 slots, user sees an overflow message.
|
|
680
|
+
const nonActiveSlots = state.slots.filter((s) => !s.active).slice(0, 3);
|
|
681
|
+
for (const slot of nonActiveSlots) {
|
|
682
|
+
kb.text(`Use: ${slot.slot}`, encodeCallbackData({ kind: "use", agent: state.agent, slot: slot.slot }));
|
|
683
|
+
}
|
|
684
|
+
if (nonActiveSlots.length > 0) kb.row();
|
|
685
|
+
|
|
686
|
+
// Slot row C: remove buttons (only for non-active slots; removing the
|
|
687
|
+
// active slot is blocked by auth-slot-parser's checkRemoveSafety).
|
|
688
|
+
const removableSlots = state.slots.filter((s) => !s.active).slice(0, 3);
|
|
689
|
+
for (const slot of removableSlots) {
|
|
690
|
+
kb.text(`🗑 Remove: ${slot.slot}`, encodeCallbackData({ kind: "rm", agent: state.agent, slot: slot.slot }));
|
|
691
|
+
}
|
|
692
|
+
if (removableSlots.length > 0) kb.row();
|
|
693
|
+
|
|
694
|
+
// Pending-flow recovery. Shown ONLY when an auth flow is
|
|
695
|
+
// pending (session meta file on disk). Lets the user explicitly
|
|
696
|
+
// kill + restart the flow.
|
|
697
|
+
if (state.pendingSessionSlot) {
|
|
698
|
+
kb.text(
|
|
699
|
+
`♻️ Restart ${state.pendingSessionSlot} flow`,
|
|
700
|
+
encodeCallbackData({ kind: "restart-flow", agent: state.agent, slot: state.pendingSessionSlot }),
|
|
701
|
+
);
|
|
702
|
+
kb.row();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Quota row. [📊 Full quota] is the escape hatch when the
|
|
706
|
+
// operator wants the live numbers behind the cached mini-bars.
|
|
707
|
+
// The legacy `[⚠️ Fall back now]` button (manual auto-fallback at
|
|
708
|
+
// the slot level) was removed in v0.6.11 — the Switch primary
|
|
709
|
+
// picker is the operator-facing surface for "active is hot, swap
|
|
710
|
+
// to a fallback," and the auto-fallback poller still handles the
|
|
711
|
+
// automatic case when the active hits its quota wall. The
|
|
712
|
+
// `fallback` callback verb stays in the parser/dispatcher for
|
|
713
|
+
// legacy reachability of any pinned messages still bearing the
|
|
714
|
+
// pre-v0.6.11 button, but no new buttons emit it.
|
|
715
|
+
kb.text("📊 Full quota", encodeCallbackData({ kind: "usage", agent: state.agent }));
|
|
716
|
+
kb.row();
|
|
717
|
+
|
|
718
|
+
// Refresh
|
|
719
|
+
kb.text("🔁 Refresh", encodeCallbackData({ kind: "refresh", agent: state.agent }));
|
|
720
|
+
|
|
721
|
+
return kb;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/** Derive the `quotaHot` flag from a slot set. Used by the gateway
|
|
725
|
+
* at dashboard-build time and by tests. */
|
|
726
|
+
export function isQuotaHot(slots: DashboardSlot[]): boolean {
|
|
727
|
+
for (const s of slots) {
|
|
728
|
+
if (s.health === "quota-exhausted") return true;
|
|
729
|
+
if ((s.fiveHourPct ?? 0) >= QUOTA_HOT_THRESHOLD_PCT) return true;
|
|
730
|
+
if ((s.sevenDayPct ?? 0) >= QUOTA_HOT_THRESHOLD_PCT) return true;
|
|
731
|
+
}
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Account-level analogue: derive the `quotaHot` flag from the
|
|
737
|
+
* accounts section of the dashboard. Under the new auth framework
|
|
738
|
+
* accounts (not slots) are the unit of quota, so the [Fall back now]
|
|
739
|
+
* affordance should fire when ANY account in the agent's list is
|
|
740
|
+
* approaching the cap — not just the slot that happens to be the
|
|
741
|
+
* active mirror.
|
|
742
|
+
*
|
|
743
|
+
* Combine with `isQuotaHot(slots)` via `||` at the call site so
|
|
744
|
+
* legacy slot setups still get the warning.
|
|
745
|
+
*/
|
|
746
|
+
export function isAccountQuotaHot(
|
|
747
|
+
accounts: ReadonlyArray<AccountSummary> | undefined,
|
|
748
|
+
): boolean {
|
|
749
|
+
if (!accounts) return false;
|
|
750
|
+
for (const a of accounts) {
|
|
751
|
+
if (a.health === "quota-exhausted") return true;
|
|
752
|
+
if ((a.fiveHourPct ?? 0) >= QUOTA_HOT_THRESHOLD_PCT) return true;
|
|
753
|
+
if ((a.sevenDayPct ?? 0) >= QUOTA_HOT_THRESHOLD_PCT) return true;
|
|
754
|
+
}
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Render the per-account quota line shown under each account row in
|
|
760
|
+
* the dashboard. Returns null when no quota data is available — the
|
|
761
|
+
* caller skips the row entirely so a freshly-added (un-probed)
|
|
762
|
+
* account doesn't show a placeholder.
|
|
763
|
+
*
|
|
764
|
+
* Format priority:
|
|
765
|
+
* - quota-exhausted (server-side or 100% utilization) →
|
|
766
|
+
* "exhausted · resets in Nh Mm"
|
|
767
|
+
* - both percentages known → "5h: 47% · 7d: 12%"
|
|
768
|
+
* - one percentage known → that one
|
|
769
|
+
* - nothing → null
|
|
770
|
+
*
|
|
771
|
+
* Reset times come straight from the Anthropic response headers via
|
|
772
|
+
* `parseQuotaHeaders` (`fiveHourResetAt`, `sevenDayResetAt` epoch ms).
|
|
773
|
+
*/
|
|
774
|
+
export function formatAccountQuotaLine(
|
|
775
|
+
acc: AccountSummary,
|
|
776
|
+
now: number = Date.now(),
|
|
777
|
+
): string | null {
|
|
778
|
+
if (acc.quotaExhaustedUntil != null && acc.quotaExhaustedUntil > now) {
|
|
779
|
+
const reset = formatRelativeMs(acc.quotaExhaustedUntil - now);
|
|
780
|
+
return `<i>exhausted · resets in ${reset}</i>`;
|
|
781
|
+
}
|
|
782
|
+
const parts: string[] = [];
|
|
783
|
+
if (acc.fiveHourPct != null) {
|
|
784
|
+
parts.push(`<i>5h:</i> ${formatQuotaPct(acc.fiveHourPct)}`);
|
|
785
|
+
}
|
|
786
|
+
if (acc.sevenDayPct != null) {
|
|
787
|
+
parts.push(`<i>7d:</i> ${formatQuotaPct(acc.sevenDayPct)}`);
|
|
788
|
+
}
|
|
789
|
+
if (parts.length === 0) return null;
|
|
790
|
+
// Append a "<window> resets in <duration>" suffix when reset
|
|
791
|
+
// timestamps are available (issue #708). Picks whichever window
|
|
792
|
+
// resets sooner (5h preferred on tie). Hidden once the reset is in
|
|
793
|
+
// the past — the percent column already shows whether the cap is
|
|
794
|
+
// free again.
|
|
795
|
+
const resetSuffix = formatNearestAccountResetSuffix(acc, now);
|
|
796
|
+
if (resetSuffix) parts.push(resetSuffix);
|
|
797
|
+
return parts.join(" · ");
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Render the "5h resets in 2h 14m" / "7d resets in 1d 3h" suffix
|
|
802
|
+
* appended to per-account quota lines (issue #708). Returns "" when
|
|
803
|
+
* neither timestamp is present or both are in the past — the parent
|
|
804
|
+
* caller decides whether to push it.
|
|
805
|
+
*
|
|
806
|
+
* Exported for the boot card so the two surfaces share one dialect.
|
|
807
|
+
*/
|
|
808
|
+
export function formatNearestAccountResetSuffix(
|
|
809
|
+
acc: Pick<AccountSummary, "fiveHourResetAt" | "sevenDayResetAt">,
|
|
810
|
+
now: number = Date.now(),
|
|
811
|
+
): string {
|
|
812
|
+
const five = acc.fiveHourResetAt;
|
|
813
|
+
const seven = acc.sevenDayResetAt;
|
|
814
|
+
let target: number | null = null;
|
|
815
|
+
let label: "5h" | "7d" | null = null;
|
|
816
|
+
if (five != null && (seven == null || five <= seven)) {
|
|
817
|
+
target = five;
|
|
818
|
+
label = "5h";
|
|
819
|
+
} else if (seven != null) {
|
|
820
|
+
target = seven;
|
|
821
|
+
label = "7d";
|
|
822
|
+
}
|
|
823
|
+
if (target == null || label == null) return "";
|
|
824
|
+
const delta = target - now;
|
|
825
|
+
if (delta <= 0) return "";
|
|
826
|
+
return `<i>${label} resets in</i> ${formatRelativeMs(delta)}`;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function formatQuotaPct(pct: number): string {
|
|
830
|
+
// Round to integer % for the dashboard. Show "<1%" when the value
|
|
831
|
+
// is positive but rounds to zero, so "0%" is reserved for genuine
|
|
832
|
+
// idle accounts.
|
|
833
|
+
const rounded = Math.round(pct);
|
|
834
|
+
if (pct > 0 && rounded === 0) return "<1%";
|
|
835
|
+
return `${rounded}%`;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Render a Unicode mini-bar for a 0–100 percentage. Six cells wide —
|
|
840
|
+
* the active row carries two of these (5h + 7d) and they need to fit
|
|
841
|
+
* one mobile line alongside the labels and percentages.
|
|
842
|
+
*
|
|
843
|
+
* formatQuotaBar(0) → ░░░░░░
|
|
844
|
+
* formatQuotaBar(47) → ███░░░
|
|
845
|
+
* formatQuotaBar(99) → █████░ (clamps below full so 99% reads
|
|
846
|
+
* visibly different from 100%)
|
|
847
|
+
* formatQuotaBar(100) → ██████
|
|
848
|
+
*
|
|
849
|
+
* Used only on the active-account row (the one running quota right
|
|
850
|
+
* now). Fallback rows still render plain percentages because the bars
|
|
851
|
+
* eat horizontal space the indented "↳" rows don't have.
|
|
852
|
+
*/
|
|
853
|
+
export function formatQuotaBar(pct: number, cells: number = 6): string {
|
|
854
|
+
if (cells <= 0) return "";
|
|
855
|
+
const clamped = Math.max(0, Math.min(100, pct));
|
|
856
|
+
// Math.floor for the filled cell count — 100% gets all cells, 99%
|
|
857
|
+
// gets cells-1, anything below the per-cell threshold gets 0.
|
|
858
|
+
const filled =
|
|
859
|
+
clamped >= 100 ? cells : Math.floor((clamped / 100) * cells);
|
|
860
|
+
return "█".repeat(filled) + "░".repeat(cells - filled);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function formatRelativeMs(ms: number): string {
|
|
864
|
+
const totalMin = Math.max(1, Math.floor(ms / 60_000));
|
|
865
|
+
if (totalMin < 60) return `${totalMin}m`;
|
|
866
|
+
const h = Math.floor(totalMin / 60);
|
|
867
|
+
const m = totalMin % 60;
|
|
868
|
+
return m === 0 ? `${h}h` : `${h}h ${m}m`;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Shorten Anthropic's verbose tier strings into something readable in a
|
|
873
|
+
* one-line dashboard header.
|
|
874
|
+
*
|
|
875
|
+
* default_claude_max_5x → max_5x
|
|
876
|
+
* default_claude_max_20x → max_20x
|
|
877
|
+
* default_claude_pro → pro
|
|
878
|
+
* anything else → passthrough (we don't pretend to
|
|
879
|
+
* understand every future tier string)
|
|
880
|
+
*/
|
|
881
|
+
export function formatRateLimitTier(tier: string): string {
|
|
882
|
+
if (!tier) return tier;
|
|
883
|
+
return tier.replace(/^default_claude_/, "");
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/** Tiny HTML escaper — same shape as welcome-text.ts's escapeHtml so
|
|
887
|
+
* this module stays dependency-free. */
|
|
888
|
+
export function escapeHtml(text: string): string {
|
|
889
|
+
return text
|
|
890
|
+
.replace(/&/g, "&")
|
|
891
|
+
.replace(/</g, "<")
|
|
892
|
+
.replace(/>/g, ">")
|
|
893
|
+
.replace(/"/g, """);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/** Build the confirmation keyboard shown when the user taps Remove.
|
|
897
|
+
* Two-step confirm prevents accidental slot deletion on mobile. */
|
|
898
|
+
export function buildRemoveConfirmKeyboard(agent: string, slot: string): InlineKeyboard {
|
|
899
|
+
return new InlineKeyboard()
|
|
900
|
+
.text(`⚠️ Confirm remove: ${slot}`, encodeCallbackData({ kind: "confirm-rm", agent, slot }))
|
|
901
|
+
.row()
|
|
902
|
+
.text("↩️ Cancel", encodeCallbackData({ kind: "refresh", agent }));
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Build the switch-primary picker keyboard. One row per non-active
|
|
907
|
+
* account (the candidates the user might promote). Each row is a
|
|
908
|
+
* direct `confirm-account-promote` — single tap fires the change, no
|
|
909
|
+
* second confirm screen, since the picker itself is already an
|
|
910
|
+
* intentional drill-down ("I tapped Switch primary, then I tapped
|
|
911
|
+
* the new primary").
|
|
912
|
+
*
|
|
913
|
+
* Why skip the two-stage confirm here when enable/disable have one:
|
|
914
|
+
* - The picker IS the confirmation surface. Showing a second
|
|
915
|
+
* "Confirm promote: foo?" screen on top of "tap the one you
|
|
916
|
+
* want" is mobile UX cruft.
|
|
917
|
+
* - The action is reversible — operators can re-promote at will.
|
|
918
|
+
*
|
|
919
|
+
* Cancel returns to the main dashboard via a refresh callback.
|
|
920
|
+
*
|
|
921
|
+
* Signature mirrors `buildAccountConfirmKeyboard` for consistency:
|
|
922
|
+
* `agent` first, then the picker-specific data (the candidates).
|
|
923
|
+
*/
|
|
924
|
+
export function buildSwitchPrimaryKeyboard(
|
|
925
|
+
agent: string,
|
|
926
|
+
candidates: ReadonlyArray<{ label: string; health: AccountHealth }>,
|
|
927
|
+
): InlineKeyboard {
|
|
928
|
+
const kb = new InlineKeyboard();
|
|
929
|
+
for (const cand of candidates) {
|
|
930
|
+
const action: CallbackAction = {
|
|
931
|
+
kind: "confirm-account-promote",
|
|
932
|
+
agent,
|
|
933
|
+
label: cand.label,
|
|
934
|
+
};
|
|
935
|
+
const encoded = encodeCallbackData(action);
|
|
936
|
+
if (Buffer.byteLength(encoded, "utf8") > CALLBACK_BUDGET_BYTES) {
|
|
937
|
+
// Pathological agent + label combo. Render the row inert so the
|
|
938
|
+
// operator falls back to the CLI rather than us silently
|
|
939
|
+
// dropping the candidate.
|
|
940
|
+
kb.text(
|
|
941
|
+
`⚠ ${truncateLabel(cand.label)} (use CLI)`,
|
|
942
|
+
encodeCallbackData({ kind: "noop" }),
|
|
943
|
+
);
|
|
944
|
+
} else {
|
|
945
|
+
kb.text(`⤴ ${cand.label}${healthSuffix(cand.health)}`, encoded);
|
|
946
|
+
}
|
|
947
|
+
kb.row();
|
|
948
|
+
}
|
|
949
|
+
kb.text("↩️ Cancel", encodeCallbackData({ kind: "refresh", agent }));
|
|
950
|
+
return kb;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Two-stage confirmation for the account promote action — mirrors
|
|
955
|
+
* `buildAccountConfirmKeyboard` but with the promote-specific verb so
|
|
956
|
+
* the confirm row's callback dispatches to `confirm-account-promote`.
|
|
957
|
+
*
|
|
958
|
+
* Why a separate helper instead of extending the existing one's `kind`
|
|
959
|
+
* parameter: the `enable | disable` discriminant is already in widely-
|
|
960
|
+
* used callsites; threading a third value through them would force
|
|
961
|
+
* cascading test updates. A dedicated helper is cleaner.
|
|
962
|
+
*/
|
|
963
|
+
export function buildAccountPromoteConfirmKeyboard(
|
|
964
|
+
agent: string,
|
|
965
|
+
label: string,
|
|
966
|
+
): InlineKeyboard {
|
|
967
|
+
const action: CallbackAction = { kind: "confirm-account-promote", agent, label };
|
|
968
|
+
return new InlineKeyboard()
|
|
969
|
+
.text(`⚠️ Confirm promote: ${label}`, encodeCallbackData(action))
|
|
970
|
+
.row()
|
|
971
|
+
.text("↩️ Cancel", encodeCallbackData({ kind: "refresh", agent }));
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* Two-stage confirmation for account toggles. Mirrors
|
|
976
|
+
* `buildRemoveConfirmKeyboard`'s shape — one confirm row + a cancel
|
|
977
|
+
* that re-renders the dashboard. `kind` selects enable vs disable so
|
|
978
|
+
* one helper covers both directions.
|
|
979
|
+
*/
|
|
980
|
+
export function buildAccountConfirmKeyboard(
|
|
981
|
+
agent: string,
|
|
982
|
+
label: string,
|
|
983
|
+
kind: "enable" | "disable",
|
|
984
|
+
): InlineKeyboard {
|
|
985
|
+
const action: CallbackAction = kind === "enable"
|
|
986
|
+
? { kind: "confirm-account-enable", agent, label }
|
|
987
|
+
: { kind: "confirm-account-disable", agent, label };
|
|
988
|
+
const verb = kind === "enable" ? "enable" : "disable";
|
|
989
|
+
return new InlineKeyboard()
|
|
990
|
+
.text(`⚠️ Confirm ${verb}: ${label}`, encodeCallbackData(action))
|
|
991
|
+
.row()
|
|
992
|
+
.text("↩️ Cancel", encodeCallbackData({ kind: "refresh", agent }));
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Health affix for the account button label. Keeps healthy accounts
|
|
997
|
+
* unadorned (the ✓/○ marker carries the enabled-here signal) and
|
|
998
|
+
* surfaces the failure modes that need operator attention. Quota and
|
|
999
|
+
* expiry use distinct icons so the user can tell which boundary the
|
|
1000
|
+
* account hit.
|
|
1001
|
+
*/
|
|
1002
|
+
function healthSuffix(health: AccountHealth): string {
|
|
1003
|
+
switch (health) {
|
|
1004
|
+
case "quota-exhausted":
|
|
1005
|
+
return " ⚠️";
|
|
1006
|
+
case "expired":
|
|
1007
|
+
case "missing-refresh-token":
|
|
1008
|
+
return " ⌛";
|
|
1009
|
+
case "missing-credentials":
|
|
1010
|
+
return " ❌";
|
|
1011
|
+
case "healthy":
|
|
1012
|
+
default:
|
|
1013
|
+
return "";
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
/** Trim long labels in the noop fallback button so the row stays
|
|
1018
|
+
* readable on a narrow mobile screen. */
|
|
1019
|
+
function truncateLabel(label: string): string {
|
|
1020
|
+
if (label.length <= 32) return label;
|
|
1021
|
+
return label.slice(0, 31) + "…";
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// ─── v3a: Per-account sub-view ────────────────────────────────────────────
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Build the per-account drill-down sub-view text. Shown when the user
|
|
1028
|
+
* taps an account row on the main dashboard.
|
|
1029
|
+
*/
|
|
1030
|
+
export function buildAccountSubViewText(agent: string, acc: AccountSummary): string {
|
|
1031
|
+
const lines: string[] = [];
|
|
1032
|
+
lines.push(`━━━ <b>Account • ${escapeHtml(acc.label)}</b> ━━━`);
|
|
1033
|
+
lines.push(`Agent: <code>${escapeHtml(agent)}</code>`);
|
|
1034
|
+
const badge = accountHealthBadge(acc.health);
|
|
1035
|
+
const suffix = healthSuffix(acc.health);
|
|
1036
|
+
lines.push(`Health: ${badge} ${acc.health}${suffix}`);
|
|
1037
|
+
if (acc.subscriptionType) {
|
|
1038
|
+
lines.push(`Type: <b>${escapeHtml(acc.subscriptionType)}</b>`);
|
|
1039
|
+
}
|
|
1040
|
+
if (acc.expiresAt) {
|
|
1041
|
+
const expiresDate = new Date(acc.expiresAt).toISOString().slice(0, 10);
|
|
1042
|
+
lines.push(`Expires: <code>${escapeHtml(expiresDate)}</code>`);
|
|
1043
|
+
}
|
|
1044
|
+
lines.push("━━━━━━━━━━━━━━━━━━━");
|
|
1045
|
+
return lines.join("\n");
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* Build the per-account drill-down keyboard.
|
|
1050
|
+
*
|
|
1051
|
+
* Reauth is visible-but-inert in v3a — no `auth account reauth` CLI
|
|
1052
|
+
* verb exists yet. The button is surfaced so the layout is complete;
|
|
1053
|
+
* the gateway handler returns a toast noting it'll land in v3b.
|
|
1054
|
+
*/
|
|
1055
|
+
export function buildAccountSubViewKeyboard(agent: string, label: string): InlineKeyboard {
|
|
1056
|
+
const reauthAction: CallbackAction = { kind: "account-reauth", agent, label };
|
|
1057
|
+
const rmAction: CallbackAction = { kind: "account-rm", agent, label };
|
|
1058
|
+
const reauthEncoded = encodeCallbackData(reauthAction);
|
|
1059
|
+
const rmEncoded = encodeCallbackData(rmAction);
|
|
1060
|
+
const kb = new InlineKeyboard();
|
|
1061
|
+
// Reauth — inert in v3a (no CLI verb). Still wired so the layout is
|
|
1062
|
+
// complete; the gateway emits a "coming in v3b" toast.
|
|
1063
|
+
if (Buffer.byteLength(reauthEncoded, "utf8") <= CALLBACK_BUDGET_BYTES) {
|
|
1064
|
+
kb.text("🔁 Reauth", reauthEncoded);
|
|
1065
|
+
} else {
|
|
1066
|
+
kb.text("🔁 Reauth (use CLI)", encodeCallbackData({ kind: "noop" }));
|
|
1067
|
+
}
|
|
1068
|
+
kb.row();
|
|
1069
|
+
// Remove — triggers confirm sub-view.
|
|
1070
|
+
if (Buffer.byteLength(rmEncoded, "utf8") <= CALLBACK_BUDGET_BYTES) {
|
|
1071
|
+
kb.text("🗑 Remove", rmEncoded);
|
|
1072
|
+
} else {
|
|
1073
|
+
kb.text("🗑 Remove (use CLI)", encodeCallbackData({ kind: "noop" }));
|
|
1074
|
+
}
|
|
1075
|
+
kb.row();
|
|
1076
|
+
// Back to main dashboard.
|
|
1077
|
+
kb.text("← Accounts", encodeCallbackData({ kind: "refresh", agent }));
|
|
1078
|
+
return kb;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Build the remove-confirm sub-view for a per-account removal.
|
|
1083
|
+
* Ports the slot-remove confirm pattern.
|
|
1084
|
+
*/
|
|
1085
|
+
export function buildAccountRemoveConfirmKeyboard(agent: string, label: string): InlineKeyboard {
|
|
1086
|
+
const confirmAction: CallbackAction = { kind: "account-rm-confirm", agent, label };
|
|
1087
|
+
const confirmEncoded = encodeCallbackData(confirmAction);
|
|
1088
|
+
return new InlineKeyboard()
|
|
1089
|
+
.text(
|
|
1090
|
+
`✓ Yes, remove`,
|
|
1091
|
+
Buffer.byteLength(confirmEncoded, "utf8") <= CALLBACK_BUDGET_BYTES
|
|
1092
|
+
? confirmEncoded
|
|
1093
|
+
: encodeCallbackData({ kind: "noop" }),
|
|
1094
|
+
)
|
|
1095
|
+
.text(
|
|
1096
|
+
"✗ Cancel",
|
|
1097
|
+
(() => {
|
|
1098
|
+
const cancelEncoded = encodeCallbackData({ kind: "account-view", agent, label });
|
|
1099
|
+
return Buffer.byteLength(cancelEncoded, "utf8") <= CALLBACK_BUDGET_BYTES
|
|
1100
|
+
? cancelEncoded
|
|
1101
|
+
: encodeCallbackData({ kind: "noop" });
|
|
1102
|
+
})(),
|
|
1103
|
+
);
|
|
1104
|
+
}
|