gentyr 1.3.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/.claude/agents/antipattern-hunter.md +176 -0
- package/.claude/agents/code-reviewer.md +205 -0
- package/.claude/agents/code-writer.md +154 -0
- package/.claude/agents/deputy-cto.md +309 -0
- package/.claude/agents/feedback-agent.md +101 -0
- package/.claude/agents/investigator.md +136 -0
- package/.claude/agents/product-manager.md +97 -0
- package/.claude/agents/project-manager.md +116 -0
- package/.claude/agents/repo-hygiene-expert.md +626 -0
- package/.claude/agents/secret-manager.md +324 -0
- package/.claude/agents/test-writer.md +354 -0
- package/.claude/commands/configure-personas.md +144 -0
- package/.claude/commands/cto-report.md +36 -0
- package/.claude/commands/demo.md +89 -0
- package/.claude/commands/deputy-cto.md +345 -0
- package/.claude/commands/hotfix.md +31 -0
- package/.claude/commands/overdrive-gentyr.md +167 -0
- package/.claude/commands/product-manager.md +32 -0
- package/.claude/commands/push-migrations.md +86 -0
- package/.claude/commands/push-secrets.md +97 -0
- package/.claude/commands/services.json.example +30 -0
- package/.claude/commands/setup-gentyr.md +396 -0
- package/.claude/commands/show.md +42 -0
- package/.claude/commands/spawn-tasks.md +79 -0
- package/.claude/commands/toggle-automation-gentyr.md +75 -0
- package/.claude/commands/toggle-product-manager.md +19 -0
- package/.claude/commands/triage.md +69 -0
- package/.claude/hooks/README.md +686 -0
- package/.claude/hooks/__tests__/README.md +129 -0
- package/.claude/hooks/agent-tracker.js +434 -0
- package/.claude/hooks/antipattern-hunter-hook.js +401 -0
- package/.claude/hooks/api-key-watcher.js +289 -0
- package/.claude/hooks/block-no-verify.js +301 -0
- package/.claude/hooks/bypass-approval-hook.js +313 -0
- package/.claude/hooks/compliance-checker.js +1309 -0
- package/.claude/hooks/config-reader.js +143 -0
- package/.claude/hooks/credential-file-guard.js +1139 -0
- package/.claude/hooks/credential-health-check.js +168 -0
- package/.claude/hooks/credential-sync-hook.js +79 -0
- package/.claude/hooks/cto-notification-hook.js +656 -0
- package/.claude/hooks/feedback-launcher.js +424 -0
- package/.claude/hooks/feedback-orchestrator.js +367 -0
- package/.claude/hooks/gentyr-splash.js +47 -0
- package/.claude/hooks/gentyr-sync.js +389 -0
- package/.claude/hooks/hourly-automation.js +3340 -0
- package/.claude/hooks/key-sync.js +899 -0
- package/.claude/hooks/lib/approval-utils.js +731 -0
- package/.claude/hooks/lib/feature-branch-helper.js +102 -0
- package/.claude/hooks/lib/worktree-manager.js +330 -0
- package/.claude/hooks/mapping-validator.js +285 -0
- package/.claude/hooks/plan-executor.js +398 -0
- package/.claude/hooks/playwright-cli-guard.js +104 -0
- package/.claude/hooks/playwright-health-check.js +71 -0
- package/.claude/hooks/pre-commit-review.js +725 -0
- package/.claude/hooks/prompts/local-spec-enforcement.md +310 -0
- package/.claude/hooks/prompts/mapping-fix.md +92 -0
- package/.claude/hooks/prompts/mapping-review.md +140 -0
- package/.claude/hooks/prompts/schema-mapper.md +185 -0
- package/.claude/hooks/prompts/spec-enforcement.md +233 -0
- package/.claude/hooks/protected-action-approval-hook.js +336 -0
- package/.claude/hooks/protected-action-gate.js +562 -0
- package/.claude/hooks/protected-actions.json +208 -0
- package/.claude/hooks/protected-actions.json.template +122 -0
- package/.claude/hooks/quota-monitor.js +490 -0
- package/.claude/hooks/reporters/jest-failure-reporter.js +401 -0
- package/.claude/hooks/reporters/playwright-failure-reporter.js +446 -0
- package/.claude/hooks/reporters/vitest-failure-reporter.js +443 -0
- package/.claude/hooks/schema-mapper-hook.js +544 -0
- package/.claude/hooks/secret-leak-detector.js +216 -0
- package/.claude/hooks/session-reviver.js +514 -0
- package/.claude/hooks/slash-command-prefetch.js +1145 -0
- package/.claude/hooks/stale-work-detector.js +205 -0
- package/.claude/hooks/stop-continue-hook.js +414 -0
- package/.claude/hooks/todo-maintenance.js +522 -0
- package/.claude/hooks/todo-processing-prompt.md +75 -0
- package/.claude/hooks/usage-optimizer.js +791 -0
- package/.claude/mcp/README.md +246 -0
- package/.claude/settings.json.template +168 -0
- package/.mcp.json.template +207 -0
- package/CLAUDE.md +340 -0
- package/CLAUDE.md.gentyr-section +89 -0
- package/LICENSE +21 -0
- package/README.md +297 -0
- package/cli/commands/init.js +471 -0
- package/cli/commands/migrate.js +132 -0
- package/cli/commands/protect.js +271 -0
- package/cli/commands/scaffold.js +48 -0
- package/cli/commands/status.js +133 -0
- package/cli/commands/sync.js +101 -0
- package/cli/commands/uninstall.js +207 -0
- package/cli/index.js +111 -0
- package/cli/lib/config-gen.js +214 -0
- package/cli/lib/resolve-framework.js +97 -0
- package/cli/lib/state.js +140 -0
- package/cli/lib/symlinks.js +260 -0
- package/docs/AUTOMATION-SYSTEMS.md +484 -0
- package/docs/BINARY-PATCHING.md +212 -0
- package/docs/CHANGELOG.md +2830 -0
- package/docs/CREDENTIAL-DETECTION.md +151 -0
- package/docs/CTO-DASHBOARD.md +476 -0
- package/docs/DEPLOYMENT-FLOW.md +477 -0
- package/docs/DEVELOPER.md +116 -0
- package/docs/Executive.md +372 -0
- package/docs/SECRET-PATHS.md +77 -0
- package/docs/SETUP-GUIDE.md +419 -0
- package/docs/STACK.md +109 -0
- package/docs/TESTING.md +440 -0
- package/docs/assets/claude-logo.svg +3 -0
- package/docs/sessions/2026-01-24-spec-suite-implementation.md +190 -0
- package/docs/sessions/2026-02-15-feedback-e2e-audit.md +484 -0
- package/docs/sessions/2026-02-20-credential-rotation-experiments.md +340 -0
- package/docs/sessions/TEST-COVERAGE-REPORT-2026-02-20.md +168 -0
- package/docs/shared/EPHEMERAL-STATE-FILES.md +115 -0
- package/docs/shared/PROTECTION-SYSTEM.md +341 -0
- package/husky/post-commit +10 -0
- package/husky/pre-commit +40 -0
- package/husky/pre-push +94 -0
- package/package.json +43 -0
- package/packages/cto-dashboard/package-lock.json +3510 -0
- package/packages/cto-dashboard/package.json +41 -0
- package/packages/cto-dashboard/pnpm-lock.yaml +2168 -0
- package/packages/mcp-servers/dist/__testUtils__/fixtures.d.ts +220 -0
- package/packages/mcp-servers/dist/__testUtils__/fixtures.d.ts.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/fixtures.js +376 -0
- package/packages/mcp-servers/dist/__testUtils__/fixtures.js.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/index.d.ts +121 -0
- package/packages/mcp-servers/dist/__testUtils__/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/index.js +180 -0
- package/packages/mcp-servers/dist/__testUtils__/index.js.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/schemas.d.ts +84 -0
- package/packages/mcp-servers/dist/__testUtils__/schemas.d.ts.map +1 -0
- package/packages/mcp-servers/dist/__testUtils__/schemas.js +309 -0
- package/packages/mcp-servers/dist/__testUtils__/schemas.js.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/index.d.ts +7 -0
- package/packages/mcp-servers/dist/agent-reports/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/index.js +8 -0
- package/packages/mcp-servers/dist/agent-reports/index.js.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/server.d.ts +22 -0
- package/packages/mcp-servers/dist/agent-reports/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/server.js +535 -0
- package/packages/mcp-servers/dist/agent-reports/server.js.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/types.d.ts +258 -0
- package/packages/mcp-servers/dist/agent-reports/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-reports/types.js +81 -0
- package/packages/mcp-servers/dist/agent-reports/types.js.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/index.d.ts +5 -0
- package/packages/mcp-servers/dist/agent-tracker/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/index.js +5 -0
- package/packages/mcp-servers/dist/agent-tracker/index.js.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/server.d.ts +12 -0
- package/packages/mcp-servers/dist/agent-tracker/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/server.js +919 -0
- package/packages/mcp-servers/dist/agent-tracker/server.js.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/types.d.ts +328 -0
- package/packages/mcp-servers/dist/agent-tracker/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/agent-tracker/types.js +128 -0
- package/packages/mcp-servers/dist/agent-tracker/types.js.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/browser-tips.d.ts +27 -0
- package/packages/mcp-servers/dist/chrome-bridge/browser-tips.d.ts.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/browser-tips.js +167 -0
- package/packages/mcp-servers/dist/chrome-bridge/browser-tips.js.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/index.d.ts +6 -0
- package/packages/mcp-servers/dist/chrome-bridge/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/index.js +6 -0
- package/packages/mcp-servers/dist/chrome-bridge/index.js.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/server.d.ts +13 -0
- package/packages/mcp-servers/dist/chrome-bridge/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/server.js +959 -0
- package/packages/mcp-servers/dist/chrome-bridge/server.js.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/types.d.ts +41 -0
- package/packages/mcp-servers/dist/chrome-bridge/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/chrome-bridge/types.js +8 -0
- package/packages/mcp-servers/dist/chrome-bridge/types.js.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/index.d.ts +8 -0
- package/packages/mcp-servers/dist/cloudflare/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/index.js +8 -0
- package/packages/mcp-servers/dist/cloudflare/index.js.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/server.d.ts +16 -0
- package/packages/mcp-servers/dist/cloudflare/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/server.js +253 -0
- package/packages/mcp-servers/dist/cloudflare/server.js.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/types.d.ts +141 -0
- package/packages/mcp-servers/dist/cloudflare/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cloudflare/types.js +53 -0
- package/packages/mcp-servers/dist/cloudflare/types.js.map +1 -0
- package/packages/mcp-servers/dist/codecov/index.d.ts +7 -0
- package/packages/mcp-servers/dist/codecov/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/codecov/index.js +7 -0
- package/packages/mcp-servers/dist/codecov/index.js.map +1 -0
- package/packages/mcp-servers/dist/codecov/server.d.ts +21 -0
- package/packages/mcp-servers/dist/codecov/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/codecov/server.js +376 -0
- package/packages/mcp-servers/dist/codecov/server.js.map +1 -0
- package/packages/mcp-servers/dist/codecov/types.d.ts +269 -0
- package/packages/mcp-servers/dist/codecov/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/codecov/types.js +128 -0
- package/packages/mcp-servers/dist/codecov/types.js.map +1 -0
- package/packages/mcp-servers/dist/cto-report/index.d.ts +9 -0
- package/packages/mcp-servers/dist/cto-report/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-report/index.js +9 -0
- package/packages/mcp-servers/dist/cto-report/index.js.map +1 -0
- package/packages/mcp-servers/dist/cto-report/server.d.ts +14 -0
- package/packages/mcp-servers/dist/cto-report/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-report/server.js +859 -0
- package/packages/mcp-servers/dist/cto-report/server.js.map +1 -0
- package/packages/mcp-servers/dist/cto-report/types.d.ts +213 -0
- package/packages/mcp-servers/dist/cto-report/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-report/types.js +29 -0
- package/packages/mcp-servers/dist/cto-report/types.js.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/index.d.ts +7 -0
- package/packages/mcp-servers/dist/cto-reports/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/index.js +8 -0
- package/packages/mcp-servers/dist/cto-reports/index.js.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/server.d.ts +20 -0
- package/packages/mcp-servers/dist/cto-reports/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/server.js +538 -0
- package/packages/mcp-servers/dist/cto-reports/server.js.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/types.d.ts +236 -0
- package/packages/mcp-servers/dist/cto-reports/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/cto-reports/types.js +77 -0
- package/packages/mcp-servers/dist/cto-reports/types.js.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/index.d.ts +7 -0
- package/packages/mcp-servers/dist/deputy-cto/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/index.js +8 -0
- package/packages/mcp-servers/dist/deputy-cto/index.js.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/server.d.ts +23 -0
- package/packages/mcp-servers/dist/deputy-cto/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/server.js +1700 -0
- package/packages/mcp-servers/dist/deputy-cto/server.js.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/types.d.ts +439 -0
- package/packages/mcp-servers/dist/deputy-cto/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/deputy-cto/types.js +102 -0
- package/packages/mcp-servers/dist/deputy-cto/types.js.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/index.d.ts +5 -0
- package/packages/mcp-servers/dist/elastic-logs/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/index.js +5 -0
- package/packages/mcp-servers/dist/elastic-logs/index.js.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/server.d.ts +18 -0
- package/packages/mcp-servers/dist/elastic-logs/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/server.js +259 -0
- package/packages/mcp-servers/dist/elastic-logs/server.js.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/types.d.ts +107 -0
- package/packages/mcp-servers/dist/elastic-logs/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/elastic-logs/types.js +31 -0
- package/packages/mcp-servers/dist/elastic-logs/types.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/index.d.ts +2 -0
- package/packages/mcp-servers/dist/feedback-explorer/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/index.js +2 -0
- package/packages/mcp-servers/dist/feedback-explorer/index.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/server.d.ts +21 -0
- package/packages/mcp-servers/dist/feedback-explorer/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/server.js +580 -0
- package/packages/mcp-servers/dist/feedback-explorer/server.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/types.d.ts +331 -0
- package/packages/mcp-servers/dist/feedback-explorer/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-explorer/types.js +40 -0
- package/packages/mcp-servers/dist/feedback-explorer/types.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/index.d.ts +9 -0
- package/packages/mcp-servers/dist/feedback-reporter/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/index.js +9 -0
- package/packages/mcp-servers/dist/feedback-reporter/index.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/server.d.ts +36 -0
- package/packages/mcp-servers/dist/feedback-reporter/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/server.js +392 -0
- package/packages/mcp-servers/dist/feedback-reporter/server.js.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/types.d.ts +152 -0
- package/packages/mcp-servers/dist/feedback-reporter/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/feedback-reporter/types.js +67 -0
- package/packages/mcp-servers/dist/feedback-reporter/types.js.map +1 -0
- package/packages/mcp-servers/dist/github/index.d.ts +7 -0
- package/packages/mcp-servers/dist/github/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/github/index.js +7 -0
- package/packages/mcp-servers/dist/github/index.js.map +1 -0
- package/packages/mcp-servers/dist/github/server.d.ts +15 -0
- package/packages/mcp-servers/dist/github/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/github/server.js +686 -0
- package/packages/mcp-servers/dist/github/server.js.map +1 -0
- package/packages/mcp-servers/dist/github/types.d.ts +660 -0
- package/packages/mcp-servers/dist/github/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/github/types.js +209 -0
- package/packages/mcp-servers/dist/github/types.js.map +1 -0
- package/packages/mcp-servers/dist/index.d.ts +30 -0
- package/packages/mcp-servers/dist/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/index.js +32 -0
- package/packages/mcp-servers/dist/index.js.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/index.d.ts +5 -0
- package/packages/mcp-servers/dist/makerkit-docs/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/index.js +5 -0
- package/packages/mcp-servers/dist/makerkit-docs/index.js.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/server.d.ts +15 -0
- package/packages/mcp-servers/dist/makerkit-docs/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/server.js +252 -0
- package/packages/mcp-servers/dist/makerkit-docs/server.js.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/types.d.ts +74 -0
- package/packages/mcp-servers/dist/makerkit-docs/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/makerkit-docs/types.js +20 -0
- package/packages/mcp-servers/dist/makerkit-docs/types.js.map +1 -0
- package/packages/mcp-servers/dist/onepassword/index.d.ts +2 -0
- package/packages/mcp-servers/dist/onepassword/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/onepassword/index.js +2 -0
- package/packages/mcp-servers/dist/onepassword/index.js.map +1 -0
- package/packages/mcp-servers/dist/onepassword/server.d.ts +2 -0
- package/packages/mcp-servers/dist/onepassword/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/onepassword/server.js +159 -0
- package/packages/mcp-servers/dist/onepassword/server.js.map +1 -0
- package/packages/mcp-servers/dist/onepassword/types.d.ts +55 -0
- package/packages/mcp-servers/dist/onepassword/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/onepassword/types.js +22 -0
- package/packages/mcp-servers/dist/onepassword/types.js.map +1 -0
- package/packages/mcp-servers/dist/playwright/helpers.d.ts +20 -0
- package/packages/mcp-servers/dist/playwright/helpers.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright/helpers.js +31 -0
- package/packages/mcp-servers/dist/playwright/helpers.js.map +1 -0
- package/packages/mcp-servers/dist/playwright/index.d.ts +5 -0
- package/packages/mcp-servers/dist/playwright/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright/index.js +5 -0
- package/packages/mcp-servers/dist/playwright/index.js.map +1 -0
- package/packages/mcp-servers/dist/playwright/server.d.ts +13 -0
- package/packages/mcp-servers/dist/playwright/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright/server.js +1201 -0
- package/packages/mcp-servers/dist/playwright/server.js.map +1 -0
- package/packages/mcp-servers/dist/playwright/types.d.ts +216 -0
- package/packages/mcp-servers/dist/playwright/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright/types.js +172 -0
- package/packages/mcp-servers/dist/playwright/types.js.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/browser-manager.d.ts +39 -0
- package/packages/mcp-servers/dist/playwright-feedback/browser-manager.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/browser-manager.js +71 -0
- package/packages/mcp-servers/dist/playwright-feedback/browser-manager.js.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/index.d.ts +5 -0
- package/packages/mcp-servers/dist/playwright-feedback/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/index.js +5 -0
- package/packages/mcp-servers/dist/playwright-feedback/index.js.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/server.d.ts +34 -0
- package/packages/mcp-servers/dist/playwright-feedback/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/server.js +538 -0
- package/packages/mcp-servers/dist/playwright-feedback/server.js.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/types.d.ts +305 -0
- package/packages/mcp-servers/dist/playwright-feedback/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/playwright-feedback/types.js +123 -0
- package/packages/mcp-servers/dist/playwright-feedback/types.js.map +1 -0
- package/packages/mcp-servers/dist/product-manager/server.d.ts +17 -0
- package/packages/mcp-servers/dist/product-manager/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/product-manager/server.js +690 -0
- package/packages/mcp-servers/dist/product-manager/server.js.map +1 -0
- package/packages/mcp-servers/dist/product-manager/types.d.ts +286 -0
- package/packages/mcp-servers/dist/product-manager/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/product-manager/types.js +99 -0
- package/packages/mcp-servers/dist/product-manager/types.js.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/index.d.ts +7 -0
- package/packages/mcp-servers/dist/programmatic-feedback/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/index.js +7 -0
- package/packages/mcp-servers/dist/programmatic-feedback/index.js.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/sandbox.d.ts +19 -0
- package/packages/mcp-servers/dist/programmatic-feedback/sandbox.d.ts.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/sandbox.js +174 -0
- package/packages/mcp-servers/dist/programmatic-feedback/sandbox.js.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/server.d.ts +35 -0
- package/packages/mcp-servers/dist/programmatic-feedback/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/server.js +465 -0
- package/packages/mcp-servers/dist/programmatic-feedback/server.js.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/types.d.ts +127 -0
- package/packages/mcp-servers/dist/programmatic-feedback/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/programmatic-feedback/types.js +80 -0
- package/packages/mcp-servers/dist/programmatic-feedback/types.js.map +1 -0
- package/packages/mcp-servers/dist/render/index.d.ts +8 -0
- package/packages/mcp-servers/dist/render/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/render/index.js +8 -0
- package/packages/mcp-servers/dist/render/index.js.map +1 -0
- package/packages/mcp-servers/dist/render/server.d.ts +15 -0
- package/packages/mcp-servers/dist/render/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/render/server.js +428 -0
- package/packages/mcp-servers/dist/render/server.js.map +1 -0
- package/packages/mcp-servers/dist/render/types.d.ts +273 -0
- package/packages/mcp-servers/dist/render/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/render/types.js +102 -0
- package/packages/mcp-servers/dist/render/types.js.map +1 -0
- package/packages/mcp-servers/dist/resend/index.d.ts +7 -0
- package/packages/mcp-servers/dist/resend/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/resend/index.js +7 -0
- package/packages/mcp-servers/dist/resend/index.js.map +1 -0
- package/packages/mcp-servers/dist/resend/server.d.ts +15 -0
- package/packages/mcp-servers/dist/resend/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/resend/server.js +298 -0
- package/packages/mcp-servers/dist/resend/server.js.map +1 -0
- package/packages/mcp-servers/dist/resend/types.d.ts +222 -0
- package/packages/mcp-servers/dist/resend/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/resend/types.js +58 -0
- package/packages/mcp-servers/dist/resend/types.js.map +1 -0
- package/packages/mcp-servers/dist/review-queue/index.d.ts +6 -0
- package/packages/mcp-servers/dist/review-queue/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/review-queue/index.js +6 -0
- package/packages/mcp-servers/dist/review-queue/index.js.map +1 -0
- package/packages/mcp-servers/dist/review-queue/server.d.ts +17 -0
- package/packages/mcp-servers/dist/review-queue/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/review-queue/server.js +348 -0
- package/packages/mcp-servers/dist/review-queue/server.js.map +1 -0
- package/packages/mcp-servers/dist/review-queue/types.d.ts +162 -0
- package/packages/mcp-servers/dist/review-queue/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/review-queue/types.js +56 -0
- package/packages/mcp-servers/dist/review-queue/types.js.map +1 -0
- package/packages/mcp-servers/dist/secret-sync/server.d.ts +19 -0
- package/packages/mcp-servers/dist/secret-sync/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/secret-sync/server.js +1139 -0
- package/packages/mcp-servers/dist/secret-sync/server.js.map +1 -0
- package/packages/mcp-servers/dist/secret-sync/types.d.ts +442 -0
- package/packages/mcp-servers/dist/secret-sync/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/secret-sync/types.js +113 -0
- package/packages/mcp-servers/dist/secret-sync/types.js.map +1 -0
- package/packages/mcp-servers/dist/session-events/index.d.ts +5 -0
- package/packages/mcp-servers/dist/session-events/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-events/index.js +5 -0
- package/packages/mcp-servers/dist/session-events/index.js.map +1 -0
- package/packages/mcp-servers/dist/session-events/server.d.ts +11 -0
- package/packages/mcp-servers/dist/session-events/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-events/server.js +290 -0
- package/packages/mcp-servers/dist/session-events/server.js.map +1 -0
- package/packages/mcp-servers/dist/session-events/types.d.ts +213 -0
- package/packages/mcp-servers/dist/session-events/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-events/types.js +69 -0
- package/packages/mcp-servers/dist/session-events/types.js.map +1 -0
- package/packages/mcp-servers/dist/session-restart/index.d.ts +9 -0
- package/packages/mcp-servers/dist/session-restart/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-restart/index.js +9 -0
- package/packages/mcp-servers/dist/session-restart/index.js.map +1 -0
- package/packages/mcp-servers/dist/session-restart/server.d.ts +20 -0
- package/packages/mcp-servers/dist/session-restart/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-restart/server.js +411 -0
- package/packages/mcp-servers/dist/session-restart/server.js.map +1 -0
- package/packages/mcp-servers/dist/session-restart/types.d.ts +26 -0
- package/packages/mcp-servers/dist/session-restart/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/session-restart/types.js +16 -0
- package/packages/mcp-servers/dist/session-restart/types.js.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/index.d.ts +5 -0
- package/packages/mcp-servers/dist/setup-helper/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/index.js +5 -0
- package/packages/mcp-servers/dist/setup-helper/index.js.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/server.d.ts +14 -0
- package/packages/mcp-servers/dist/setup-helper/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/server.js +454 -0
- package/packages/mcp-servers/dist/setup-helper/server.js.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/types.d.ts +81 -0
- package/packages/mcp-servers/dist/setup-helper/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/setup-helper/types.js +41 -0
- package/packages/mcp-servers/dist/setup-helper/types.js.map +1 -0
- package/packages/mcp-servers/dist/shared/audited-server.d.ts +31 -0
- package/packages/mcp-servers/dist/shared/audited-server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/audited-server.js +126 -0
- package/packages/mcp-servers/dist/shared/audited-server.js.map +1 -0
- package/packages/mcp-servers/dist/shared/constants.d.ts +26 -0
- package/packages/mcp-servers/dist/shared/constants.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/constants.js +41 -0
- package/packages/mcp-servers/dist/shared/constants.js.map +1 -0
- package/packages/mcp-servers/dist/shared/index.d.ts +6 -0
- package/packages/mcp-servers/dist/shared/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/index.js +6 -0
- package/packages/mcp-servers/dist/shared/index.js.map +1 -0
- package/packages/mcp-servers/dist/shared/readonly-db.d.ts +11 -0
- package/packages/mcp-servers/dist/shared/readonly-db.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/readonly-db.js +47 -0
- package/packages/mcp-servers/dist/shared/readonly-db.js.map +1 -0
- package/packages/mcp-servers/dist/shared/resolve-framework.d.ts +20 -0
- package/packages/mcp-servers/dist/shared/resolve-framework.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/resolve-framework.js +65 -0
- package/packages/mcp-servers/dist/shared/resolve-framework.js.map +1 -0
- package/packages/mcp-servers/dist/shared/server.d.ts +86 -0
- package/packages/mcp-servers/dist/shared/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/server.js +291 -0
- package/packages/mcp-servers/dist/shared/server.js.map +1 -0
- package/packages/mcp-servers/dist/shared/types.d.ts +113 -0
- package/packages/mcp-servers/dist/shared/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/shared/types.js +36 -0
- package/packages/mcp-servers/dist/shared/types.js.map +1 -0
- package/packages/mcp-servers/dist/show/server.d.ts +12 -0
- package/packages/mcp-servers/dist/show/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/show/server.js +97 -0
- package/packages/mcp-servers/dist/show/server.js.map +1 -0
- package/packages/mcp-servers/dist/show/types.d.ts +19 -0
- package/packages/mcp-servers/dist/show/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/show/types.js +32 -0
- package/packages/mcp-servers/dist/show/types.js.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/index.d.ts +5 -0
- package/packages/mcp-servers/dist/specs-browser/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/index.js +5 -0
- package/packages/mcp-servers/dist/specs-browser/index.js.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/server.d.ts +13 -0
- package/packages/mcp-servers/dist/specs-browser/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/server.js +692 -0
- package/packages/mcp-servers/dist/specs-browser/server.js.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/types.d.ts +337 -0
- package/packages/mcp-servers/dist/specs-browser/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/specs-browser/types.js +134 -0
- package/packages/mcp-servers/dist/specs-browser/types.js.map +1 -0
- package/packages/mcp-servers/dist/supabase/index.d.ts +10 -0
- package/packages/mcp-servers/dist/supabase/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/supabase/index.js +10 -0
- package/packages/mcp-servers/dist/supabase/index.js.map +1 -0
- package/packages/mcp-servers/dist/supabase/server.d.ts +20 -0
- package/packages/mcp-servers/dist/supabase/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/supabase/server.js +451 -0
- package/packages/mcp-servers/dist/supabase/server.js.map +1 -0
- package/packages/mcp-servers/dist/supabase/types.d.ts +196 -0
- package/packages/mcp-servers/dist/supabase/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/supabase/types.js +76 -0
- package/packages/mcp-servers/dist/supabase/types.js.map +1 -0
- package/packages/mcp-servers/dist/todo-db/index.d.ts +5 -0
- package/packages/mcp-servers/dist/todo-db/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/todo-db/index.js +5 -0
- package/packages/mcp-servers/dist/todo-db/index.js.map +1 -0
- package/packages/mcp-servers/dist/todo-db/server.d.ts +13 -0
- package/packages/mcp-servers/dist/todo-db/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/todo-db/server.js +649 -0
- package/packages/mcp-servers/dist/todo-db/server.js.map +1 -0
- package/packages/mcp-servers/dist/todo-db/types.d.ts +225 -0
- package/packages/mcp-servers/dist/todo-db/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/todo-db/types.js +69 -0
- package/packages/mcp-servers/dist/todo-db/types.js.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/index.d.ts +7 -0
- package/packages/mcp-servers/dist/user-feedback/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/index.js +8 -0
- package/packages/mcp-servers/dist/user-feedback/index.js.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/server.d.ts +25 -0
- package/packages/mcp-servers/dist/user-feedback/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/server.js +914 -0
- package/packages/mcp-servers/dist/user-feedback/server.js.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/types.d.ts +415 -0
- package/packages/mcp-servers/dist/user-feedback/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/user-feedback/types.js +132 -0
- package/packages/mcp-servers/dist/user-feedback/types.js.map +1 -0
- package/packages/mcp-servers/dist/vercel/index.d.ts +9 -0
- package/packages/mcp-servers/dist/vercel/index.d.ts.map +1 -0
- package/packages/mcp-servers/dist/vercel/index.js +9 -0
- package/packages/mcp-servers/dist/vercel/index.js.map +1 -0
- package/packages/mcp-servers/dist/vercel/server.d.ts +17 -0
- package/packages/mcp-servers/dist/vercel/server.d.ts.map +1 -0
- package/packages/mcp-servers/dist/vercel/server.js +265 -0
- package/packages/mcp-servers/dist/vercel/server.js.map +1 -0
- package/packages/mcp-servers/dist/vercel/types.d.ts +189 -0
- package/packages/mcp-servers/dist/vercel/types.d.ts.map +1 -0
- package/packages/mcp-servers/dist/vercel/types.js +65 -0
- package/packages/mcp-servers/dist/vercel/types.js.map +1 -0
- package/packages/mcp-servers/package-lock.json +3765 -0
- package/packages/mcp-servers/package.json +64 -0
- package/packages/mcp-servers/test/reporters/test-failure-reporter.ts +372 -0
- package/packages/mcp-servers/vitest.config.ts +27 -0
- package/scripts/__tests__/README.md +163 -0
- package/scripts/apply-credential-hardening.sh +271 -0
- package/scripts/credential-providers/manual.js +56 -0
- package/scripts/credential-providers/onepassword.js +85 -0
- package/scripts/credential-providers/provider-interface.js +104 -0
- package/scripts/encrypt-credential.js +337 -0
- package/scripts/feedback-launcher.js +338 -0
- package/scripts/feedback-orchestrator.js +373 -0
- package/scripts/fix-mcp-launcher-issues.sh +97 -0
- package/scripts/force-spawn-tasks.js +651 -0
- package/scripts/force-triage-reports.js +560 -0
- package/scripts/generate-protected-actions-spec.js +142 -0
- package/scripts/generate-proxy-certs.sh +158 -0
- package/scripts/grant-chrome-ext-permissions.sh +242 -0
- package/scripts/mcp-launcher.js +125 -0
- package/scripts/merge-settings.cjs +167 -0
- package/scripts/patch-clawd.py +844 -0
- package/scripts/patch-credential-cache.py +313 -0
- package/scripts/patches/credential-file-guard-patched.mjs +573 -0
- package/scripts/patches/credential-file-guard.js.patched +573 -0
- package/scripts/patches/verify-tokenizer.mjs +132 -0
- package/scripts/protect-framework.sh +478 -0
- package/scripts/readme-chrome.template +12 -0
- package/scripts/reap-completed-agents.js +439 -0
- package/scripts/reinstall.sh +86 -0
- package/scripts/resign-node.sh +185 -0
- package/scripts/rotation-proxy.js +656 -0
- package/scripts/rotation-stress-monitor.mjs +862 -0
- package/scripts/setup-automation-service.sh +648 -0
- package/scripts/setup-check.js +251 -0
- package/scripts/watch-claude-version.js +142 -0
- package/specs/framework/CORE-INVARIANTS.md +161 -0
- package/specs/patterns/AGENT-PATTERNS.md +223 -0
- package/specs/patterns/HOOK-PATTERNS.md +242 -0
- package/specs/patterns/MCP-SERVER-PATTERNS.md +144 -0
- package/templates/config/gitignore.template +14 -0
- package/templates/config/merge-chain-check.yml.template +51 -0
- package/templates/config/package.json.template +18 -0
- package/templates/config/pnpm-workspace.yaml +5 -0
- package/templates/config/services.json.template +18 -0
- package/templates/config/tsconfig.base.json +17 -0
- package/templates/scaffold/integrations/_template/.gitkeep +0 -0
- package/templates/scaffold/packages/logger/package.json +17 -0
- package/templates/scaffold/packages/logger/src/logger.ts +44 -0
- package/templates/scaffold/packages/shared/package.json +17 -0
- package/templates/scaffold/packages/shared/src/errors.ts +43 -0
- package/templates/scaffold/products/_product/apps/backend/package.json +21 -0
- package/templates/scaffold/products/_product/apps/backend/src/index.ts +17 -0
- package/templates/scaffold/products/_product/apps/extension/.gitkeep +0 -0
- package/templates/scaffold/products/_product/apps/web/.gitkeep +0 -0
- package/templates/scaffold/specs/global/.gitkeep +0 -0
- package/templates/scaffold/specs/local/.gitkeep +0 -0
- package/templates/scaffold/specs/reference/.gitkeep +0 -0
- package/version.json +15 -0
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage Optimizer
|
|
3
|
+
*
|
|
4
|
+
* Tracks API quota utilization every 10 minutes and dynamically adjusts
|
|
5
|
+
* automation spawn rates to target 90% usage at reset time.
|
|
6
|
+
*
|
|
7
|
+
* Called as the first step in hourly-automation.js on every 10-minute invocation.
|
|
8
|
+
*
|
|
9
|
+
* Process:
|
|
10
|
+
* 1. Snapshot: Fetch usage from Anthropic API for all keys in api-key-rotation.json
|
|
11
|
+
* 2. Trajectory: Calculate linear usage rate from recent snapshots (needs 3+)
|
|
12
|
+
* 3. Adjustment: Compare projected-at-reset to 90% target, compute factor
|
|
13
|
+
*
|
|
14
|
+
* @version 1.0.0
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as fs from 'fs';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import * as os from 'os';
|
|
20
|
+
import { execFileSync } from 'child_process';
|
|
21
|
+
import { getConfigPath, getDefaults } from './config-reader.js';
|
|
22
|
+
|
|
23
|
+
const PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
24
|
+
const ROTATION_STATE_PATH = path.join(os.homedir(), '.claude', 'api-key-rotation.json');
|
|
25
|
+
const OLD_PROJECT_ROTATION_PATH = path.join(PROJECT_DIR, '.claude', 'api-key-rotation.json');
|
|
26
|
+
const SNAPSHOTS_PATH = path.join(PROJECT_DIR, '.claude', 'state', 'usage-snapshots.json');
|
|
27
|
+
const CREDENTIALS_PATH = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
28
|
+
const ANTHROPIC_API_URL = 'https://api.anthropic.com/api/oauth/usage';
|
|
29
|
+
const ANTHROPIC_BETA_HEADER = 'oauth-2025-04-20';
|
|
30
|
+
|
|
31
|
+
const TARGET_UTILIZATION = 0.90;
|
|
32
|
+
const MAX_FACTOR = 20.0; // up to 20x speedup (MIN_EFFECTIVE_MINUTES is the real ceiling)
|
|
33
|
+
const MIN_FACTOR = 0.05; // up to 20x slowdown
|
|
34
|
+
const MAX_CHANGE_PER_CYCLE = 0.10; // ±10% per cycle
|
|
35
|
+
const SNAPSHOT_RETENTION_DAYS = 7;
|
|
36
|
+
const MIN_SNAPSHOTS_FOR_TRAJECTORY = 3;
|
|
37
|
+
const MIN_EFFECTIVE_MINUTES = 5; // Floor: no cooldown can go below 5 minutes
|
|
38
|
+
const MAX_COOLDOWN_MINUTES = {
|
|
39
|
+
production_health_monitor: 120, // 2h max (default 60) — never throttle production health beyond 2h
|
|
40
|
+
staging_health_monitor: 360, // 6h max (default 180) — cap staging health throttling
|
|
41
|
+
triage_check: 15, // 15min max (default 5) — keep triage responsive
|
|
42
|
+
};
|
|
43
|
+
const SINGLE_KEY_WARNING_THRESHOLD = 0.80; // Warn when any key exceeds 80%
|
|
44
|
+
const PER_KEY_RESET_DROP_THRESHOLD = 0.50; // Detect reset when ANY key's 5h drops >50pp
|
|
45
|
+
const MIN_SNAPSHOT_INTERVAL_MS = 5 * 60 * 1000; // 5 min — throttle between snapshots
|
|
46
|
+
const EMA_WINDOW_MS = 2 * 60 * 60 * 1000; // 2 hours — time window for EMA
|
|
47
|
+
const EMA_MIN_INTERVAL_MS = 5 * 60 * 1000; // 5 min — dedup interval for EMA input
|
|
48
|
+
const MIN_HOURS_DELTA = 0.05; // 3 min — floor for EMA interval pairs
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Revert overdrive state, restoring previous effective values and factor.
|
|
52
|
+
*/
|
|
53
|
+
function revertOverdrive(config, log) {
|
|
54
|
+
const prev = config.overdrive.previous_state;
|
|
55
|
+
if (prev?.effective) {
|
|
56
|
+
config.effective = prev.effective;
|
|
57
|
+
}
|
|
58
|
+
if (prev?.factor !== undefined) {
|
|
59
|
+
const restoredFactor = Math.max(MIN_FACTOR, Math.min(MAX_FACTOR, prev.factor));
|
|
60
|
+
config.adjustment = config.adjustment || {};
|
|
61
|
+
config.adjustment.factor = restoredFactor;
|
|
62
|
+
config.adjustment.last_updated = new Date().toISOString();
|
|
63
|
+
config.adjustment.direction = 'overdrive-reverted';
|
|
64
|
+
}
|
|
65
|
+
config.overdrive.active = false;
|
|
66
|
+
|
|
67
|
+
const configPath = getConfigPath();
|
|
68
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
69
|
+
log(`Usage optimizer: Overdrive expired, reverted to previous state (factor: ${prev?.factor ?? 1.0})`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get the timestamp of the most recent snapshot from the snapshots file.
|
|
74
|
+
* Returns null if no snapshots exist or file is unreadable.
|
|
75
|
+
*/
|
|
76
|
+
function getLastSnapshotTimestamp() {
|
|
77
|
+
try {
|
|
78
|
+
if (!fs.existsSync(SNAPSHOTS_PATH)) return null;
|
|
79
|
+
const data = JSON.parse(fs.readFileSync(SNAPSHOTS_PATH, 'utf8'));
|
|
80
|
+
if (!data || !Array.isArray(data.snapshots) || data.snapshots.length === 0) return null;
|
|
81
|
+
return data.snapshots[data.snapshots.length - 1]?.ts || null;
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Select time-based snapshots from an array, deduplicating by minimum interval.
|
|
89
|
+
* Walks backward from the most recent snapshot, only including entries at least
|
|
90
|
+
* minIntervalMs apart, stopping when windowMs is exceeded.
|
|
91
|
+
*
|
|
92
|
+
* @param {Array} snapshots - All snapshots (must have .ts)
|
|
93
|
+
* @param {number} windowMs - Maximum time window from most recent snapshot
|
|
94
|
+
* @param {number} minIntervalMs - Minimum interval between selected snapshots
|
|
95
|
+
* @returns {Array} Selected snapshots in chronological order
|
|
96
|
+
*/
|
|
97
|
+
function selectTimeBasedSnapshots(snapshots, windowMs, minIntervalMs) {
|
|
98
|
+
if (!snapshots || snapshots.length === 0) return [];
|
|
99
|
+
|
|
100
|
+
const latest = snapshots[snapshots.length - 1];
|
|
101
|
+
const windowStart = latest.ts - windowMs;
|
|
102
|
+
const selected = [latest];
|
|
103
|
+
let lastSelectedTs = latest.ts;
|
|
104
|
+
|
|
105
|
+
for (let i = snapshots.length - 2; i >= 0; i--) {
|
|
106
|
+
const s = snapshots[i];
|
|
107
|
+
if (s.ts < windowStart) break;
|
|
108
|
+
if (lastSelectedTs - s.ts >= minIntervalMs) {
|
|
109
|
+
selected.push(s);
|
|
110
|
+
lastSelectedTs = s.ts;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Fall back to array tail if window yields fewer than 3 snapshots (cold start)
|
|
115
|
+
if (selected.length < 3) {
|
|
116
|
+
return snapshots.slice(-30);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return selected.reverse(); // chronological order
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Main entry point - run the usage optimizer.
|
|
124
|
+
* Designed to be cheap and fast (one API call + math).
|
|
125
|
+
*
|
|
126
|
+
* @param {function} [logFn] - Optional log function (default: console.log)
|
|
127
|
+
* @returns {Promise<{success: boolean, snapshotTaken: boolean, adjustmentMade: boolean, error?: string}>}
|
|
128
|
+
*/
|
|
129
|
+
export async function runUsageOptimizer(logFn) {
|
|
130
|
+
const log = logFn || console.log;
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
// Snapshot throttle: skip collection if last snapshot is less than 5 minutes old
|
|
134
|
+
const lastTs = getLastSnapshotTimestamp();
|
|
135
|
+
if (lastTs && (Date.now() - lastTs) < MIN_SNAPSHOT_INTERVAL_MS) {
|
|
136
|
+
return { success: true, snapshotTaken: false, adjustmentMade: false };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Overdrive check: skip adjustment if overdrive is active
|
|
140
|
+
try {
|
|
141
|
+
const configPath = getConfigPath();
|
|
142
|
+
if (fs.existsSync(configPath)) {
|
|
143
|
+
const overdriveConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
144
|
+
if (overdriveConfig.overdrive?.active) {
|
|
145
|
+
if (new Date() > new Date(overdriveConfig.overdrive.expires_at)) {
|
|
146
|
+
// Expired - revert and continue with normal adjustment
|
|
147
|
+
revertOverdrive(overdriveConfig, log);
|
|
148
|
+
} else {
|
|
149
|
+
// Active - take snapshot but skip adjustment
|
|
150
|
+
log(`Usage optimizer: Overdrive active until ${overdriveConfig.overdrive.expires_at}, skipping cooldown adjustment.`);
|
|
151
|
+
const snapshot = await collectSnapshot(log);
|
|
152
|
+
if (snapshot) {
|
|
153
|
+
storeSnapshot(snapshot, log);
|
|
154
|
+
}
|
|
155
|
+
return { success: true, snapshotTaken: !!snapshot, adjustmentMade: false };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch (err) {
|
|
160
|
+
log(`Usage optimizer: Overdrive check failed (non-fatal): ${err.message}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Step 1: Collect usage snapshot
|
|
164
|
+
const snapshot = await collectSnapshot(log);
|
|
165
|
+
if (!snapshot) {
|
|
166
|
+
return { success: true, snapshotTaken: false, adjustmentMade: false };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Step 2: Store snapshot
|
|
170
|
+
storeSnapshot(snapshot, log);
|
|
171
|
+
|
|
172
|
+
// Step 3: Calculate trajectory and adjust (if enough data)
|
|
173
|
+
const adjusted = calculateAndAdjust(log);
|
|
174
|
+
|
|
175
|
+
return { success: true, snapshotTaken: true, adjustmentMade: adjusted };
|
|
176
|
+
} catch (err) {
|
|
177
|
+
log(`Usage optimizer error: ${err.message}`);
|
|
178
|
+
return { success: false, snapshotTaken: false, adjustmentMade: false, error: err.message };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Fetch usage data from all tracked API keys.
|
|
184
|
+
* Returns null if no keys available or API unreachable.
|
|
185
|
+
*/
|
|
186
|
+
async function collectSnapshot(log) {
|
|
187
|
+
const keys = getApiKeys();
|
|
188
|
+
if (keys.length === 0) {
|
|
189
|
+
log('Usage optimizer: No API keys found, skipping snapshot.');
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const ts = Date.now();
|
|
194
|
+
const rawKeyData = {};
|
|
195
|
+
const keyLookup = new Map();
|
|
196
|
+
|
|
197
|
+
for (const key of keys) {
|
|
198
|
+
keyLookup.set(key.id, key);
|
|
199
|
+
try {
|
|
200
|
+
const usage = await fetchUsage(key.accessToken);
|
|
201
|
+
if (usage) {
|
|
202
|
+
rawKeyData[key.id] = {
|
|
203
|
+
'5h': (usage.fiveHour.utilization ?? 0) / 100,
|
|
204
|
+
'5h_reset': usage.fiveHour.resetsAt,
|
|
205
|
+
'7d': (usage.sevenDay.utilization ?? 0) / 100,
|
|
206
|
+
'7d_reset': usage.sevenDay.resetsAt,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
} catch (err) {
|
|
210
|
+
log(`Usage optimizer: Failed to fetch usage for key ${key.id}: ${err.message}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (Object.keys(rawKeyData).length === 0) {
|
|
215
|
+
log('Usage optimizer: No usage data retrieved, skipping snapshot.');
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Deduplicate: group by account, keep first key per account.
|
|
220
|
+
// Uses account_uuid as primary dedup key, falls back to a fingerprint
|
|
221
|
+
// of usage values (same principle as cto-notification-hook.js).
|
|
222
|
+
const accountMap = new Map();
|
|
223
|
+
for (const [keyId, usage] of Object.entries(rawKeyData)) {
|
|
224
|
+
const key = keyLookup.get(keyId);
|
|
225
|
+
const dedupeKey = key?.accountId
|
|
226
|
+
|| `fp:${usage['5h']}:${usage['7d']}`;
|
|
227
|
+
if (!accountMap.has(dedupeKey)) {
|
|
228
|
+
accountMap.set(dedupeKey, { id: keyId, usage });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Build snapshot from deduplicated accounts
|
|
233
|
+
const keyData = {};
|
|
234
|
+
for (const [, entry] of accountMap) {
|
|
235
|
+
keyData[entry.id] = entry.usage;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return { ts, keys: keyData };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get API keys from rotation state or credentials file.
|
|
243
|
+
* Returns array of { id, accessToken }.
|
|
244
|
+
*/
|
|
245
|
+
function getApiKeys() {
|
|
246
|
+
const keys = [];
|
|
247
|
+
const now = Date.now();
|
|
248
|
+
|
|
249
|
+
// Source 1: Environment variable override (highest priority)
|
|
250
|
+
const envToken = process.env['CLAUDE_CODE_OAUTH_TOKEN'];
|
|
251
|
+
if (envToken) {
|
|
252
|
+
return [{ id: 'env', accessToken: envToken, accountId: null }];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Source 2: Rotation state (multiple keys) - check user-level, fallback to project-level
|
|
256
|
+
const rotationPath = fs.existsSync(ROTATION_STATE_PATH) ? ROTATION_STATE_PATH
|
|
257
|
+
: fs.existsSync(OLD_PROJECT_ROTATION_PATH) ? OLD_PROJECT_ROTATION_PATH
|
|
258
|
+
: null;
|
|
259
|
+
if (rotationPath) {
|
|
260
|
+
try {
|
|
261
|
+
const state = JSON.parse(fs.readFileSync(rotationPath, 'utf8'));
|
|
262
|
+
if (state && state.keys && typeof state.keys === 'object') {
|
|
263
|
+
for (const [id, data] of Object.entries(state.keys)) {
|
|
264
|
+
if (!data.accessToken) continue;
|
|
265
|
+
// Skip expired or invalid keys
|
|
266
|
+
if (data.status === 'expired' || data.status === 'invalid') continue;
|
|
267
|
+
if (data.expiresAt && data.expiresAt < now) continue;
|
|
268
|
+
keys.push({ id: id.substring(0, 8), accessToken: data.accessToken, accountId: data.account_uuid || null });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.error(`[usage-optimizer] Failed to read rotation state: ${err.message}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Source 3: macOS Keychain (matches data-reader.ts:getCredentialToken)
|
|
277
|
+
if (keys.length === 0 && process.platform === 'darwin') {
|
|
278
|
+
try {
|
|
279
|
+
const { username } = os.userInfo();
|
|
280
|
+
const raw = execFileSync('security', [
|
|
281
|
+
'find-generic-password', '-s', 'Claude Code-credentials', '-a', username, '-w',
|
|
282
|
+
], { encoding: 'utf8', timeout: 3000 }).trim();
|
|
283
|
+
const creds = JSON.parse(raw);
|
|
284
|
+
if (creds?.claudeAiOauth?.accessToken) {
|
|
285
|
+
const expiresAt = creds.claudeAiOauth.expiresAt;
|
|
286
|
+
if (!expiresAt || expiresAt > now) {
|
|
287
|
+
keys.push({ id: 'keychain', accessToken: creds.claudeAiOauth.accessToken, accountId: null });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} catch {
|
|
291
|
+
// Keychain not available (locked, no entry, or non-macOS)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Source 4: Credentials file (~/.claude/.credentials.json)
|
|
296
|
+
if (keys.length === 0 && fs.existsSync(CREDENTIALS_PATH)) {
|
|
297
|
+
try {
|
|
298
|
+
const creds = JSON.parse(fs.readFileSync(CREDENTIALS_PATH, 'utf8'));
|
|
299
|
+
if (creds?.claudeAiOauth?.accessToken) {
|
|
300
|
+
keys.push({ id: 'default', accessToken: creds.claudeAiOauth.accessToken, accountId: null });
|
|
301
|
+
}
|
|
302
|
+
} catch (err) {
|
|
303
|
+
console.error(`[usage-optimizer] Failed to read credentials: ${err.message}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return keys;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Fetch usage from Anthropic API for a single key.
|
|
312
|
+
* Returns { fiveHour: { utilization, resetsAt }, sevenDay: { utilization, resetsAt } } or null.
|
|
313
|
+
*/
|
|
314
|
+
async function fetchUsage(accessToken) {
|
|
315
|
+
const response = await fetch(ANTHROPIC_API_URL, {
|
|
316
|
+
method: 'GET',
|
|
317
|
+
headers: {
|
|
318
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
319
|
+
'Content-Type': 'application/json',
|
|
320
|
+
'User-Agent': 'claude-code/2.1.14',
|
|
321
|
+
'anthropic-beta': ANTHROPIC_BETA_HEADER,
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
if (!response.ok) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const data = await response.json();
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
fiveHour: {
|
|
333
|
+
utilization: data.five_hour?.utilization ?? 0,
|
|
334
|
+
resetsAt: data.five_hour?.resets_at ?? null,
|
|
335
|
+
},
|
|
336
|
+
sevenDay: {
|
|
337
|
+
utilization: data.seven_day?.utilization ?? 0,
|
|
338
|
+
resetsAt: data.seven_day?.resets_at ?? null,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Store a snapshot and prune old entries.
|
|
345
|
+
*/
|
|
346
|
+
function storeSnapshot(snapshot, log) {
|
|
347
|
+
let data = { snapshots: [] };
|
|
348
|
+
|
|
349
|
+
if (fs.existsSync(SNAPSHOTS_PATH)) {
|
|
350
|
+
try {
|
|
351
|
+
data = JSON.parse(fs.readFileSync(SNAPSHOTS_PATH, 'utf8'));
|
|
352
|
+
if (!data || !Array.isArray(data.snapshots)) {
|
|
353
|
+
data = { snapshots: [] };
|
|
354
|
+
}
|
|
355
|
+
} catch {
|
|
356
|
+
data = { snapshots: [] };
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Migrate old-format snapshots (0-100 scale → 0-1 fraction)
|
|
361
|
+
for (const s of data.snapshots) {
|
|
362
|
+
if (!s.keys) continue;
|
|
363
|
+
for (const k of Object.values(s.keys)) {
|
|
364
|
+
if ((k['5h'] ?? 0) > 1.0) k['5h'] = k['5h'] / 100;
|
|
365
|
+
if ((k['7d'] ?? 0) > 1.0) k['7d'] = k['7d'] / 100;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
data.snapshots.push(snapshot);
|
|
370
|
+
|
|
371
|
+
// Prune entries older than retention period
|
|
372
|
+
const cutoff = Date.now() - (SNAPSHOT_RETENTION_DAYS * 24 * 60 * 60 * 1000);
|
|
373
|
+
data.snapshots = data.snapshots.filter(s => s.ts > cutoff);
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
fs.writeFileSync(SNAPSHOTS_PATH, JSON.stringify(data, null, 2));
|
|
377
|
+
} catch (err) {
|
|
378
|
+
log(`Usage optimizer: Failed to write snapshots: ${err.message}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Calculate trajectory and adjust cooldowns.
|
|
384
|
+
* Returns true if an adjustment was made.
|
|
385
|
+
*/
|
|
386
|
+
function calculateAndAdjust(log) {
|
|
387
|
+
let data;
|
|
388
|
+
try {
|
|
389
|
+
data = JSON.parse(fs.readFileSync(SNAPSHOTS_PATH, 'utf8'));
|
|
390
|
+
} catch {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (!data || !data.snapshots || data.snapshots.length < MIN_SNAPSHOTS_FOR_TRAJECTORY) {
|
|
395
|
+
log(`Usage optimizer: Only ${data?.snapshots?.length ?? 0} snapshots, need ${MIN_SNAPSHOTS_FOR_TRAJECTORY}. Skipping adjustment.`);
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Reset-boundary detection: if ANY single key's 5h utilization dropped >50pp
|
|
400
|
+
// between consecutive snapshots, a window reset just happened. Skip this cycle
|
|
401
|
+
// to avoid the stale rate causing the factor to ramp up blindly.
|
|
402
|
+
// Per-key detection avoids dilution when some keys are already at 0%.
|
|
403
|
+
if (data.snapshots.length >= 2) {
|
|
404
|
+
const prev = data.snapshots[data.snapshots.length - 2];
|
|
405
|
+
const curr = data.snapshots[data.snapshots.length - 1];
|
|
406
|
+
const commonKeys = Object.keys(curr.keys).filter(k => k in prev.keys);
|
|
407
|
+
for (const k of commonKeys) {
|
|
408
|
+
const keyDrop = (prev.keys[k]['5h'] ?? 0) - (curr.keys[k]['5h'] ?? 0);
|
|
409
|
+
if (keyDrop >= PER_KEY_RESET_DROP_THRESHOLD) {
|
|
410
|
+
log(`Usage optimizer: Reset boundary detected (key ${k} 5h dropped ${Math.round(keyDrop * 100)}pp). Skipping adjustment cycle.`);
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Key-count discontinuity guard: when key count changes between consecutive
|
|
417
|
+
// snapshots (key discovery/removal), skip one cycle to let EMA stabilize.
|
|
418
|
+
if (data.snapshots.length >= 2) {
|
|
419
|
+
const prevSnap = data.snapshots[data.snapshots.length - 2];
|
|
420
|
+
const currSnap = data.snapshots[data.snapshots.length - 1];
|
|
421
|
+
const prevKeyCount = Object.keys(prevSnap.keys).length;
|
|
422
|
+
const currKeyCount = Object.keys(currSnap.keys).length;
|
|
423
|
+
if (prevKeyCount !== currKeyCount) {
|
|
424
|
+
log(`Usage optimizer: Key count changed (${prevKeyCount} → ${currKeyCount}). Skipping adjustment cycle to stabilize EMA.`);
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Get the most relevant metrics (aggregate across keys)
|
|
430
|
+
const latest = data.snapshots[data.snapshots.length - 1];
|
|
431
|
+
const timeBasedWindow = selectTimeBasedSnapshots(data.snapshots, EMA_WINDOW_MS, EMA_MIN_INTERVAL_MS);
|
|
432
|
+
const earliest = timeBasedWindow[0]; // First snapshot in time-based window
|
|
433
|
+
|
|
434
|
+
const hoursBetween = (latest.ts - earliest.ts) / (1000 * 60 * 60);
|
|
435
|
+
if (hoursBetween < 0.15) { // Less than ~10 minutes of data
|
|
436
|
+
log('Usage optimizer: Not enough time span for trajectory. Skipping.');
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Calculate aggregate metrics across all keys (with EMA from all snapshots)
|
|
441
|
+
const aggregate = calculateAggregate(latest, earliest, hoursBetween, data.snapshots);
|
|
442
|
+
if (!aggregate) {
|
|
443
|
+
log('Usage optimizer: Could not calculate aggregate metrics.');
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Determine constraining metric
|
|
448
|
+
// Cap projections to prevent runaway extrapolation — linear rate projection
|
|
449
|
+
// over long horizons (e.g. 155h for 7d) produces nonsensical values that
|
|
450
|
+
// pin the factor at MIN_FACTOR permanently.
|
|
451
|
+
const MAX_PROJECTION = 1.5;
|
|
452
|
+
const projected5h = Math.min(MAX_PROJECTION, aggregate.current5h + (aggregate.rate5h * aggregate.hoursUntil5hReset));
|
|
453
|
+
const projected7d = Math.min(MAX_PROJECTION, aggregate.current7d + (aggregate.rate7d * aggregate.hoursUntil7dReset));
|
|
454
|
+
const constraining = projected5h > projected7d ? '5h' : '7d';
|
|
455
|
+
const projectedAtReset = Math.max(projected5h, projected7d);
|
|
456
|
+
|
|
457
|
+
// Read current config
|
|
458
|
+
const configPath = getConfigPath();
|
|
459
|
+
let config;
|
|
460
|
+
try {
|
|
461
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
462
|
+
} catch {
|
|
463
|
+
log('Usage optimizer: Config file unreadable, skipping adjustment.');
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const currentFactor = config.adjustment?.factor ?? 1.0;
|
|
468
|
+
const currentUsage = constraining === '5h' ? aggregate.current5h : aggregate.current7d;
|
|
469
|
+
const currentRate = constraining === '5h' ? aggregate.rate5h : aggregate.rate7d;
|
|
470
|
+
const hoursUntilReset = constraining === '5h' ? aggregate.hoursUntil5hReset : aggregate.hoursUntil7dReset;
|
|
471
|
+
|
|
472
|
+
// Tier 1 recovery: if factor is stuck at minimum but current usage is well below
|
|
473
|
+
// target, the projection model was unreliable. Reset factor to baseline so
|
|
474
|
+
// automations aren't permanently throttled.
|
|
475
|
+
// 0.15 threshold = 85%+ slower than default; catches factors that drifted very low
|
|
476
|
+
if (currentFactor <= 0.15 && currentUsage < TARGET_UTILIZATION * 0.7) {
|
|
477
|
+
applyFactor(config, 1.0, constraining, projectedAtReset, log, hoursUntilReset);
|
|
478
|
+
log(`Usage optimizer: Factor recovery (tier 1) — usage at ${Math.round(currentUsage * 100)}% (well below ${Math.round(TARGET_UTILIZATION * 100)}% target) but factor stuck at minimum. Reset to 1.0.`);
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Tier 2 recovery: gradual recovery for factors in the 0.15-0.5 dead zone.
|
|
483
|
+
// The 5% per-cycle conservative speedup is too slow to recover from this range.
|
|
484
|
+
if (currentFactor < 0.5 && currentFactor > 0.15 && currentUsage < TARGET_UTILIZATION * 0.8) {
|
|
485
|
+
const recoveredFactor = Math.min(1.0, currentFactor * 1.5);
|
|
486
|
+
applyFactor(config, recoveredFactor, constraining, projectedAtReset, log, hoursUntilReset);
|
|
487
|
+
log(`Usage optimizer: Factor recovery (tier 2) — usage at ${Math.round(currentUsage * 100)}% with factor at ${currentFactor.toFixed(3)}. Boosted to ${recoveredFactor.toFixed(3)}.`);
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Per-key warnings: flag any key exceeding the warning threshold
|
|
492
|
+
if (aggregate.perKeyUtilization) {
|
|
493
|
+
for (const [keyId, util] of Object.entries(aggregate.perKeyUtilization)) {
|
|
494
|
+
if (util['5h'] >= SINGLE_KEY_WARNING_THRESHOLD) {
|
|
495
|
+
log(`Usage optimizer WARNING: Account ${keyId} at ${Math.round(util['5h'] * 100)}% 5h utilization`);
|
|
496
|
+
}
|
|
497
|
+
if (util['7d'] >= SINGLE_KEY_WARNING_THRESHOLD) {
|
|
498
|
+
log(`Usage optimizer WARNING: Account ${keyId} at ${Math.round(util['7d'] * 100)}% 7d utilization`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Bias currentUsage upward if any single key is near exhaustion
|
|
504
|
+
let effectiveUsage = currentUsage;
|
|
505
|
+
const maxKeyUsage = constraining === '5h' ? aggregate.maxKey5h : aggregate.maxKey7d;
|
|
506
|
+
if (maxKeyUsage >= SINGLE_KEY_WARNING_THRESHOLD) {
|
|
507
|
+
effectiveUsage = Math.max(effectiveUsage, maxKeyUsage * 0.8);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Edge case: already at or above target
|
|
511
|
+
if (effectiveUsage >= TARGET_UTILIZATION) {
|
|
512
|
+
// Never speed up if already near cap - clamp factor to <= 1.0
|
|
513
|
+
const newFactor = Math.min(currentFactor, 1.0);
|
|
514
|
+
if (newFactor !== currentFactor) {
|
|
515
|
+
applyFactor(config, newFactor, constraining, projectedAtReset, log, hoursUntilReset);
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
log(`Usage optimizer: Already at ${Math.round(effectiveUsage * 100)}% usage. Holding steady. Reset in ${hoursUntilReset.toFixed(1)}h.`);
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Edge case: rate is zero or negative (usage flat/decreasing)
|
|
523
|
+
if (currentRate <= 0) {
|
|
524
|
+
// Conservatively speed up (5% per cycle, capped at MAX_FACTOR)
|
|
525
|
+
const newFactor = Math.min(currentFactor * 1.05, MAX_FACTOR);
|
|
526
|
+
if (Math.abs(newFactor - currentFactor) > 0.001) {
|
|
527
|
+
applyFactor(config, newFactor, constraining, projectedAtReset, log, hoursUntilReset);
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Normal case: calculate desired rate to hit target at reset
|
|
534
|
+
const desiredRate = (TARGET_UTILIZATION - effectiveUsage) / hoursUntilReset;
|
|
535
|
+
const rawRatio = desiredRate / currentRate;
|
|
536
|
+
|
|
537
|
+
// Conservative bounds: max ±10% per cycle
|
|
538
|
+
const clamped = Math.max(1.0 - MAX_CHANGE_PER_CYCLE, Math.min(1.0 + MAX_CHANGE_PER_CYCLE, rawRatio));
|
|
539
|
+
let newFactor = currentFactor * clamped;
|
|
540
|
+
|
|
541
|
+
// Overall bounds
|
|
542
|
+
newFactor = Math.max(MIN_FACTOR, Math.min(MAX_FACTOR, newFactor));
|
|
543
|
+
|
|
544
|
+
// Only apply if meaningful change
|
|
545
|
+
if (Math.abs(newFactor - currentFactor) < 0.01) {
|
|
546
|
+
log(`Usage optimizer: Factor unchanged (${currentFactor.toFixed(2)}). On track for ${Math.round(projectedAtReset * 100)}% at reset. Reset in ${hoursUntilReset.toFixed(1)}h.`);
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
applyFactor(config, newFactor, constraining, projectedAtReset, log, hoursUntilReset);
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Calculate EMA-smoothed rate from an array of snapshots.
|
|
556
|
+
* Uses exponential moving average of per-interval deltas for smoother estimation.
|
|
557
|
+
*
|
|
558
|
+
* @param {Array} snapshots - Array of raw snapshots (must have .ts and .keys)
|
|
559
|
+
* @param {'5h'|'7d'} metricKey - Which metric to compute rate for
|
|
560
|
+
* @param {number} [alpha=0.3] - EMA smoothing factor (higher = more weight on recent)
|
|
561
|
+
* @returns {number} Smoothed rate per hour
|
|
562
|
+
*/
|
|
563
|
+
function calculateEmaRate(snapshots, metricKey, alpha = 0.3, excludeKeys = null) {
|
|
564
|
+
if (snapshots.length < 2) return 0;
|
|
565
|
+
|
|
566
|
+
let emaRate = null;
|
|
567
|
+
|
|
568
|
+
for (let i = 1; i < snapshots.length; i++) {
|
|
569
|
+
const prev = snapshots[i - 1];
|
|
570
|
+
const curr = snapshots[i];
|
|
571
|
+
const hoursDelta = (curr.ts - prev.ts) / (1000 * 60 * 60);
|
|
572
|
+
if (hoursDelta < MIN_HOURS_DELTA) continue; // Skip rapid-fire intervals (<3 min)
|
|
573
|
+
|
|
574
|
+
// Average across common keys for this pair, excluding exhausted keys
|
|
575
|
+
let commonKeys = Object.keys(curr.keys).filter(k => k in prev.keys);
|
|
576
|
+
if (excludeKeys) commonKeys = commonKeys.filter(k => !excludeKeys.has(k));
|
|
577
|
+
if (commonKeys.length === 0) continue;
|
|
578
|
+
|
|
579
|
+
let sumCurr = 0, sumPrev = 0;
|
|
580
|
+
for (const k of commonKeys) {
|
|
581
|
+
sumCurr += curr.keys[k][metricKey] ?? 0;
|
|
582
|
+
sumPrev += prev.keys[k][metricKey] ?? 0;
|
|
583
|
+
}
|
|
584
|
+
const avgCurr = sumCurr / commonKeys.length;
|
|
585
|
+
const avgPrev = sumPrev / commonKeys.length;
|
|
586
|
+
const intervalRate = (avgCurr - avgPrev) / hoursDelta;
|
|
587
|
+
|
|
588
|
+
if (emaRate === null) {
|
|
589
|
+
emaRate = intervalRate;
|
|
590
|
+
} else {
|
|
591
|
+
emaRate = alpha * intervalRate + (1 - alpha) * emaRate;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return emaRate ?? 0;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Calculate aggregate metrics across all keys in a snapshot pair.
|
|
600
|
+
* Uses EMA-smoothed rates from recent snapshots for stability.
|
|
601
|
+
* Also tracks per-key utilization and max values across keys.
|
|
602
|
+
*/
|
|
603
|
+
function calculateAggregate(latest, earliest, hoursBetween, allSnapshots) {
|
|
604
|
+
const latestEntries = Object.entries(latest.keys);
|
|
605
|
+
if (latestEntries.length === 0) return null;
|
|
606
|
+
|
|
607
|
+
// Classify keys as exhausted (7d >= 0.995) vs active
|
|
608
|
+
const EXHAUSTED_THRESHOLD = 0.995;
|
|
609
|
+
const exhaustedKeyIds = new Set();
|
|
610
|
+
let resetAt5h = null, resetAt7d = null;
|
|
611
|
+
const perKeyUtilization = {};
|
|
612
|
+
|
|
613
|
+
// First pass: classify keys, collect per-key data and reset times
|
|
614
|
+
for (const [id, k] of latestEntries) {
|
|
615
|
+
const val5h = k['5h'] ?? 0;
|
|
616
|
+
const val7d = k['7d'] ?? 0;
|
|
617
|
+
perKeyUtilization[id] = { '5h': val5h, '7d': val7d };
|
|
618
|
+
if (k['5h_reset'] && (!resetAt5h || k['5h_reset'] < resetAt5h)) resetAt5h = k['5h_reset'];
|
|
619
|
+
if (k['7d_reset'] && (!resetAt7d || k['7d_reset'] < resetAt7d)) resetAt7d = k['7d_reset'];
|
|
620
|
+
if (val7d >= EXHAUSTED_THRESHOLD) exhaustedKeyIds.add(id);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Compute aggregate from active keys only; fall back to all-key average if ALL exhausted
|
|
624
|
+
const activeEntries = latestEntries.filter(([id]) => !exhaustedKeyIds.has(id));
|
|
625
|
+
const entriesToAverage = activeEntries.length > 0 ? activeEntries : latestEntries;
|
|
626
|
+
|
|
627
|
+
// Compute maxKey from active keys only so exhausted keys don't bias effectiveUsage
|
|
628
|
+
let maxKey5h = 0, maxKey7d = 0;
|
|
629
|
+
for (const [, k] of entriesToAverage) {
|
|
630
|
+
maxKey5h = Math.max(maxKey5h, k['5h'] ?? 0);
|
|
631
|
+
maxKey7d = Math.max(maxKey7d, k['7d'] ?? 0);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
let sum5h = 0, sum7d = 0;
|
|
635
|
+
for (const [, k] of entriesToAverage) {
|
|
636
|
+
sum5h += k['5h'] ?? 0;
|
|
637
|
+
sum7d += k['7d'] ?? 0;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const numKeys = entriesToAverage.length;
|
|
641
|
+
const current5h = sum5h / numKeys;
|
|
642
|
+
const current7d = sum7d / numKeys;
|
|
643
|
+
|
|
644
|
+
// Calculate rates: use EMA from recent snapshots if available, fall back to two-point slope
|
|
645
|
+
// Exclude exhausted keys from rate calculation so rates reflect only growing accounts
|
|
646
|
+
let rate5h = 0, rate7d = 0;
|
|
647
|
+
const excludeKeys = exhaustedKeyIds.size > 0 && activeEntries.length > 0 ? exhaustedKeyIds : null;
|
|
648
|
+
if (allSnapshots && allSnapshots.length >= 3) {
|
|
649
|
+
const recentSnapshots = selectTimeBasedSnapshots(allSnapshots, EMA_WINDOW_MS, EMA_MIN_INTERVAL_MS);
|
|
650
|
+
rate5h = calculateEmaRate(recentSnapshots, '5h', 0.3, excludeKeys);
|
|
651
|
+
rate7d = calculateEmaRate(recentSnapshots, '7d', 0.3, excludeKeys);
|
|
652
|
+
} else {
|
|
653
|
+
// Fallback: two-point slope from common keys (excluding exhausted)
|
|
654
|
+
let commonKeyIds = latestEntries
|
|
655
|
+
.map(([id]) => id)
|
|
656
|
+
.filter(id => id in earliest.keys);
|
|
657
|
+
if (excludeKeys) commonKeyIds = commonKeyIds.filter(id => !excludeKeys.has(id));
|
|
658
|
+
|
|
659
|
+
if (commonKeyIds.length > 0 && hoursBetween > 0) {
|
|
660
|
+
let latestCommon5h = 0, latestCommon7d = 0;
|
|
661
|
+
let earliestCommon5h = 0, earliestCommon7d = 0;
|
|
662
|
+
|
|
663
|
+
for (const id of commonKeyIds) {
|
|
664
|
+
latestCommon5h += latest.keys[id]['5h'] ?? 0;
|
|
665
|
+
latestCommon7d += latest.keys[id]['7d'] ?? 0;
|
|
666
|
+
earliestCommon5h += earliest.keys[id]['5h'] ?? 0;
|
|
667
|
+
earliestCommon7d += earliest.keys[id]['7d'] ?? 0;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const avg5hNow = latestCommon5h / commonKeyIds.length;
|
|
671
|
+
const avg7dNow = latestCommon7d / commonKeyIds.length;
|
|
672
|
+
const avg5hPrev = earliestCommon5h / commonKeyIds.length;
|
|
673
|
+
const avg7dPrev = earliestCommon7d / commonKeyIds.length;
|
|
674
|
+
|
|
675
|
+
rate5h = (avg5hNow - avg5hPrev) / hoursBetween;
|
|
676
|
+
rate7d = (avg7dNow - avg7dPrev) / hoursBetween;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Calculate hours until reset
|
|
681
|
+
const now = Date.now();
|
|
682
|
+
let hoursUntil5hReset = 5; // default fallback
|
|
683
|
+
let hoursUntil7dReset = 168; // default fallback (7 days)
|
|
684
|
+
|
|
685
|
+
if (resetAt5h) {
|
|
686
|
+
const resetTime = new Date(resetAt5h).getTime();
|
|
687
|
+
hoursUntil5hReset = Math.max(0.1, (resetTime - now) / (1000 * 60 * 60));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (resetAt7d) {
|
|
691
|
+
const resetTime = new Date(resetAt7d).getTime();
|
|
692
|
+
hoursUntil7dReset = Math.max(0.1, (resetTime - now) / (1000 * 60 * 60));
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return {
|
|
696
|
+
current5h, current7d, rate5h, rate7d,
|
|
697
|
+
hoursUntil5hReset, hoursUntil7dReset,
|
|
698
|
+
maxKey5h, maxKey7d, perKeyUtilization,
|
|
699
|
+
activeKeyCount: activeEntries.length,
|
|
700
|
+
totalKeyCount: latestEntries.length,
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Apply a new factor to the config, recalculating all effective cooldowns.
|
|
706
|
+
*/
|
|
707
|
+
function applyFactor(config, newFactor, constraining, projectedAtReset, log, hoursUntilReset) {
|
|
708
|
+
const previousFactor = config.adjustment?.factor ?? 1.0;
|
|
709
|
+
const defaults = getDefaults();
|
|
710
|
+
|
|
711
|
+
// Calculate effective cooldowns: higher factor = shorter cooldowns = more activity
|
|
712
|
+
const effective = {};
|
|
713
|
+
for (const [key, defaultVal] of Object.entries(defaults)) {
|
|
714
|
+
// Skip static-mode automations - they keep their fixed interval
|
|
715
|
+
if (config.modes?.[key]?.mode === 'static') {
|
|
716
|
+
effective[key] = config.modes[key].static_minutes ?? defaultVal;
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
let computed = Math.max(MIN_EFFECTIVE_MINUTES, Math.round(defaultVal / newFactor));
|
|
720
|
+
if (MAX_COOLDOWN_MINUTES[key] !== undefined) {
|
|
721
|
+
computed = Math.min(computed, MAX_COOLDOWN_MINUTES[key]);
|
|
722
|
+
}
|
|
723
|
+
effective[key] = computed;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const direction = newFactor > previousFactor + 0.005 ? 'ramping up' : newFactor < previousFactor - 0.005 ? 'ramping down' : 'holding';
|
|
727
|
+
|
|
728
|
+
config.effective = effective;
|
|
729
|
+
config.adjustment = {
|
|
730
|
+
factor: Math.round(newFactor * 1000) / 1000, // 3 decimal places
|
|
731
|
+
last_updated: new Date().toISOString(),
|
|
732
|
+
constraining_metric: constraining,
|
|
733
|
+
projected_at_reset: Math.round(projectedAtReset * 1000) / 1000,
|
|
734
|
+
direction,
|
|
735
|
+
hours_until_reset: hoursUntilReset != null ? Math.round(hoursUntilReset * 10) / 10 : null,
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
const configPath = getConfigPath();
|
|
739
|
+
try {
|
|
740
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
741
|
+
const resetStr = hoursUntilReset != null ? ` Reset in ${hoursUntilReset.toFixed(1)}h.` : '';
|
|
742
|
+
log(`Usage optimizer: Factor ${newFactor.toFixed(3)} (was ${previousFactor.toFixed(3)}), ${direction}. ` +
|
|
743
|
+
`Constraining: ${constraining}. Projected at reset: ${Math.round(projectedAtReset * 100)}%.${resetStr}`);
|
|
744
|
+
} catch (err) {
|
|
745
|
+
log(`Usage optimizer: Failed to write config: ${err.message}`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Reset the optimizer by clearing all snapshots and restoring factor to 1.0.
|
|
751
|
+
* Used after deploying fixes to flush polluted historical data.
|
|
752
|
+
*
|
|
753
|
+
* @param {function} [logFn] - Optional log function (default: console.log)
|
|
754
|
+
*/
|
|
755
|
+
export function resetOptimizer(logFn) {
|
|
756
|
+
const log = logFn || console.log;
|
|
757
|
+
|
|
758
|
+
// Clear snapshots
|
|
759
|
+
try {
|
|
760
|
+
if (fs.existsSync(SNAPSHOTS_PATH)) {
|
|
761
|
+
fs.writeFileSync(SNAPSHOTS_PATH, JSON.stringify({ snapshots: [] }, null, 2));
|
|
762
|
+
log('Usage optimizer reset: Cleared usage snapshots.');
|
|
763
|
+
}
|
|
764
|
+
} catch (err) {
|
|
765
|
+
log(`Usage optimizer reset: Failed to clear snapshots: ${err.message}`);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Reset factor to 1.0 with default cooldowns
|
|
769
|
+
try {
|
|
770
|
+
const configPath = getConfigPath();
|
|
771
|
+
if (fs.existsSync(configPath)) {
|
|
772
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
773
|
+
const defaults = getDefaults();
|
|
774
|
+
|
|
775
|
+
config.effective = { ...defaults };
|
|
776
|
+
config.adjustment = {
|
|
777
|
+
factor: 1.0,
|
|
778
|
+
last_updated: new Date().toISOString(),
|
|
779
|
+
constraining_metric: null,
|
|
780
|
+
projected_at_reset: null,
|
|
781
|
+
direction: 'reset',
|
|
782
|
+
hours_until_reset: null,
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
786
|
+
log('Usage optimizer reset: Factor restored to 1.0 with default cooldowns.');
|
|
787
|
+
}
|
|
788
|
+
} catch (err) {
|
|
789
|
+
log(`Usage optimizer reset: Failed to reset config: ${err.message}`);
|
|
790
|
+
}
|
|
791
|
+
}
|