ashlr-plugin 1.36.4
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/.agents/plugins/marketplace.json +20 -0
- package/.claude-plugin/marketplace.json +39 -0
- package/.claude-plugin/plugin.json +36 -0
- package/.codex/agents/README.md +9 -0
- package/.codex/agents/ashlr-explorer.md +36 -0
- package/.codex/agents/ashlr-worker.md +38 -0
- package/.codex-plugin/plugin.json +45 -0
- package/.mcp.json +14 -0
- package/LICENSE +21 -0
- package/README.md +311 -0
- package/agents/ashlr-code.md +69 -0
- package/agents/ashlr-explore.md +44 -0
- package/agents/ashlr-plan.md +54 -0
- package/bench/fixtures/notebook-large.ipynb +660 -0
- package/bench/fixtures/notebook-small.ipynb +39 -0
- package/bench/fixtures/tasklist-closed.json +62 -0
- package/bench/fixtures/tasklist-large-mixed.json +27 -0
- package/bench/fixtures/tasklist-sprint-open.json +79 -0
- package/bench/fixtures/websearch-bun-runtime.json +32 -0
- package/bench/fixtures/websearch-react-hooks-patterns.json +32 -0
- package/bench/fixtures/websearch-typescript-generics.json +38 -0
- package/bench/refs/node-sdk/.refrev +1 -0
- package/bench/refs/node-sdk/README.md +17 -0
- package/bench/refs/node-sdk/src/agent/agent.ts +163 -0
- package/bench/refs/node-sdk/src/agent/create-agent-ui-stream.ts +83 -0
- package/bench/refs/node-sdk/src/agent/tool-loop-agent.ts +238 -0
- package/bench/refs/node-sdk/src/embed/embed-many.ts +409 -0
- package/bench/refs/node-sdk/src/embed/embed-result.ts +50 -0
- package/bench/refs/node-sdk/src/embed/embed.ts +257 -0
- package/bench/refs/node-sdk/src/error/invalid-argument-error.ts +34 -0
- package/bench/refs/node-sdk/src/error/invalid-tool-input-error.ts +33 -0
- package/bench/refs/node-sdk/src/error/no-object-generated-error.ts +70 -0
- package/bench/refs/node-sdk/src/generate-image/generate-image-result.ts +42 -0
- package/bench/refs/node-sdk/src/generate-image/generate-image.ts +362 -0
- package/bench/refs/node-sdk/src/generate-object/generate-object-result.ts +67 -0
- package/bench/refs/node-sdk/src/generate-object/generate-object.ts +501 -0
- package/bench/refs/node-sdk/src/generate-object/inject-json-instruction.ts +30 -0
- package/bench/refs/node-sdk/src/generate-object/output-strategy.ts +415 -0
- package/bench/refs/node-sdk/src/generate-object/repair-text.ts +12 -0
- package/bench/refs/node-sdk/src/generate-object/stream-object.ts +960 -0
- package/bench/refs/node-sdk/src/generate-speech/generate-speech.ts +191 -0
- package/bench/refs/node-sdk/src/generate-text/generate-text.ts +1484 -0
- package/bench/refs/node-sdk/src/generate-text/stream-text.ts +2708 -0
- package/bench/refs/node-sdk/src/generate-text/tool-call.ts +59 -0
- package/bench/refs/node-sdk/src/generate-text/tool-result.ts +36 -0
- package/bench/refs/node-sdk/src/telemetry/create-telemetry-dispatcher.ts +146 -0
- package/bench/refs/node-sdk/src/telemetry/telemetry-registry.ts +15 -0
- package/bench/refs/node-sdk/src/telemetry/telemetry.ts +223 -0
- package/bench/refs/node-sdk/src/util/async-iterable-stream.ts +94 -0
- package/bench/refs/node-sdk/src/util/consume-stream.ts +31 -0
- package/bench/refs/node-sdk/src/util/create-stitchable-stream.ts +112 -0
- package/bench/refs/node-sdk/src/util/download/download.ts +74 -0
- package/bench/refs/node-sdk/src/util/error-handler.ts +1 -0
- package/bench/refs/node-sdk/src/util/fix-json.ts +401 -0
- package/bench/refs/node-sdk/src/util/merge-objects.ts +79 -0
- package/bench/refs/python-lib/.refrev +1 -0
- package/bench/refs/python-lib/README.md +18 -0
- package/bench/refs/python-lib/pandas/core/_numba/executor.py +249 -0
- package/bench/refs/python-lib/pandas/core/_numba/extensions.py +586 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/mean_.py +198 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/min_max_.py +178 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/sum_.py +255 -0
- package/bench/refs/python-lib/pandas/core/_numba/kernels/var_.py +251 -0
- package/bench/refs/python-lib/pandas/core/accessor.py +520 -0
- package/bench/refs/python-lib/pandas/core/api.py +138 -0
- package/bench/refs/python-lib/pandas/core/array_algos/masked_accumulations.py +97 -0
- package/bench/refs/python-lib/pandas/core/array_algos/masked_reductions.py +252 -0
- package/bench/refs/python-lib/pandas/core/array_algos/putmask.py +150 -0
- package/bench/refs/python-lib/pandas/core/array_algos/quantile.py +232 -0
- package/bench/refs/python-lib/pandas/core/array_algos/replace.py +159 -0
- package/bench/refs/python-lib/pandas/core/array_algos/take.py +609 -0
- package/bench/refs/python-lib/pandas/core/arraylike.py +534 -0
- package/bench/refs/python-lib/pandas/core/arrays/_arrow_string_mixins.py +434 -0
- package/bench/refs/python-lib/pandas/core/arrays/_mixins.py +630 -0
- package/bench/refs/python-lib/pandas/core/arrays/_ranges.py +276 -0
- package/bench/refs/python-lib/pandas/core/arrays/_utils.py +77 -0
- package/bench/refs/python-lib/pandas/core/arrays/arrow/accessors.py +521 -0
- package/bench/refs/python-lib/pandas/core/arrays/arrow/extension_types.py +174 -0
- package/bench/refs/python-lib/pandas/core/arrays/boolean.py +454 -0
- package/bench/refs/python-lib/pandas/core/arrays/floating.py +192 -0
- package/bench/refs/python-lib/pandas/core/arrays/integer.py +296 -0
- package/bench/refs/python-lib/pandas/core/arrays/masked.py +2140 -0
- package/bench/refs/python-lib/pandas/core/arrays/numeric.py +305 -0
- package/bench/refs/python-lib/pandas/core/arrays/numpy_.py +701 -0
- package/bench/refs/python-lib/pandas/core/arrays/period.py +1586 -0
- package/bench/refs/python-lib/pandas/core/arrays/sparse/accessor.py +505 -0
- package/bench/refs/python-lib/pandas/core/arrays/sparse/scipy_sparse.py +208 -0
- package/bench/refs/rust-project/.refrev +1 -0
- package/bench/refs/rust-project/README.md +17 -0
- package/bench/refs/rust-project/tokio/src/doc/os.rs +68 -0
- package/bench/refs/rust-project/tokio/src/fs/dir_builder.rs +129 -0
- package/bench/refs/rust-project/tokio/src/fs/file.rs +1113 -0
- package/bench/refs/rust-project/tokio/src/fs/mocks.rs +176 -0
- package/bench/refs/rust-project/tokio/src/fs/mod.rs +324 -0
- package/bench/refs/rust-project/tokio/src/fs/open_options.rs +856 -0
- package/bench/refs/rust-project/tokio/src/fs/read.rs +80 -0
- package/bench/refs/rust-project/tokio/src/fs/read_dir.rs +357 -0
- package/bench/refs/rust-project/tokio/src/fs/write.rs +99 -0
- package/bench/refs/rust-project/tokio/src/future/maybe_done.rs +123 -0
- package/bench/refs/rust-project/tokio/src/future/try_join.rs +82 -0
- package/bench/refs/rust-project/tokio/src/io/async_buf_read.rs +117 -0
- package/bench/refs/rust-project/tokio/src/io/async_read.rs +133 -0
- package/bench/refs/rust-project/tokio/src/io/async_seek.rs +96 -0
- package/bench/refs/rust-project/tokio/src/io/async_write.rs +402 -0
- package/bench/refs/rust-project/tokio/src/io/blocking.rs +339 -0
- package/bench/refs/rust-project/tokio/src/io/interest.rs +345 -0
- package/bench/refs/rust-project/tokio/src/io/join.rs +130 -0
- package/bench/refs/rust-project/tokio/src/io/mod.rs +308 -0
- package/bench/refs/rust-project/tokio/src/io/poll_evented.rs +319 -0
- package/bench/refs/rust-project/tokio/src/io/read_buf.rs +344 -0
- package/bench/refs/rust-project/tokio/src/io/ready.rs +322 -0
- package/bench/refs/rust-project/tokio/src/io/split.rs +166 -0
- package/bench/refs/rust-project/tokio/src/io/stderr.rs +148 -0
- package/bench/refs/rust-project/tokio/src/io/stdin.rs +98 -0
- package/bench/refs/rust-project/tokio/src/io/stdio_common.rs +222 -0
- package/bench/refs/rust-project/tokio/src/io/stdout.rs +197 -0
- package/bench/refs/rust-project/tokio/src/io/util/buf_reader.rs +311 -0
- package/bench/refs/rust-project/tokio/src/io/util/buf_stream.rs +207 -0
- package/bench/refs/rust-project/tokio/src/io/util/buf_writer.rs +310 -0
- package/bench/refs/rust-project/tokio/src/io/util/chain.rs +144 -0
- package/commands/_spawn-patterns.json +32 -0
- package/commands/ashlr-allow.md +21 -0
- package/commands/ashlr-benchmark.md +53 -0
- package/commands/ashlr-brief.md +97 -0
- package/commands/ashlr-budget.md +76 -0
- package/commands/ashlr-compact.md +54 -0
- package/commands/ashlr-dashboard.md +37 -0
- package/commands/ashlr-doctor.md +56 -0
- package/commands/ashlr-eco-mode.md +92 -0
- package/commands/ashlr-genome-init.md +24 -0
- package/commands/ashlr-genome-keygen.md +36 -0
- package/commands/ashlr-genome-loop.md +118 -0
- package/commands/ashlr-genome-push.md +36 -0
- package/commands/ashlr-genome-rewrap.md +48 -0
- package/commands/ashlr-genome-team-init.md +67 -0
- package/commands/ashlr-handoff.md +23 -0
- package/commands/ashlr-help.md +86 -0
- package/commands/ashlr-hook-timings.md +30 -0
- package/commands/ashlr-leaderboard.md +50 -0
- package/commands/ashlr-ollama-setup.md +39 -0
- package/commands/ashlr-orchestrate-status.md +72 -0
- package/commands/ashlr-orchestrate.md +83 -0
- package/commands/ashlr-parallelize.md +63 -0
- package/commands/ashlr-report-crash.md +33 -0
- package/commands/ashlr-resume.md +72 -0
- package/commands/ashlr-savings.md +24 -0
- package/commands/ashlr-settings.md +50 -0
- package/commands/ashlr-spawn.md +61 -0
- package/commands/ashlr-start.md +60 -0
- package/commands/ashlr-status.md +64 -0
- package/commands/ashlr-team-invite.md +47 -0
- package/commands/ashlr-tier.md +122 -0
- package/commands/ashlr-tour.md +60 -0
- package/commands/ashlr-update.md +155 -0
- package/commands/ashlr-upgrade.md +96 -0
- package/commands/goal.md +58 -0
- package/commands/loop.md +40 -0
- package/docs/CNAME +1 -0
- package/docs/PRO_TIER.md +104 -0
- package/docs/README.md +21 -0
- package/docs/architecture.md +415 -0
- package/docs/ashlr-pipe.md +161 -0
- package/docs/assets/logo.svg +19 -0
- package/docs/assets/og.png +0 -0
- package/docs/assets/og.svg +41 -0
- package/docs/benchmarks-v2.json +355 -0
- package/docs/benchmarks.json +102 -0
- package/docs/benchmarks.md +211 -0
- package/docs/billing.md +185 -0
- package/docs/cloud-genome.md +155 -0
- package/docs/competitive-wozcode.md +92 -0
- package/docs/deploy.md +325 -0
- package/docs/email-templates.md +113 -0
- package/docs/embed-tuning.md +103 -0
- package/docs/genome.md +120 -0
- package/docs/github-oauth-onboarding.md +127 -0
- package/docs/hero-video-script.md +129 -0
- package/docs/index.html +2262 -0
- package/docs/install-prompt.md +78 -0
- package/docs/install-windows.md +132 -0
- package/docs/install.ps1 +171 -0
- package/docs/install.sh +146 -0
- package/docs/launch/README.md +114 -0
- package/docs/launch/hn.md +49 -0
- package/docs/launch/reddit.md +45 -0
- package/docs/launch/x.md +65 -0
- package/docs/launch-post.md +112 -0
- package/docs/legal.md +78 -0
- package/docs/llm-providers.md +99 -0
- package/docs/multi-host-mcp.md +217 -0
- package/docs/multi-turn-architecture.md +108 -0
- package/docs/operations/deployment.md +108 -0
- package/docs/operations/error-monitoring.md +70 -0
- package/docs/operations/weekly-digest.md +70 -0
- package/docs/operations.md +494 -0
- package/docs/platform-support.md +133 -0
- package/docs/policy-packs.md +188 -0
- package/docs/press-kit/fact-sheet.md +78 -0
- package/docs/press-kit/founder-bio.md +3 -0
- package/docs/press-kit/logo.svg +33 -0
- package/docs/press-kit/screenshots.md +146 -0
- package/docs/pricing.md +251 -0
- package/docs/pro-backend-architecture.md +523 -0
- package/docs/producthunt.md +52 -0
- package/docs/router-migration.md +110 -0
- package/docs/session-log-schema.md +68 -0
- package/docs/social.md +95 -0
- package/docs/team-genome.md +528 -0
- package/docs/telemetry.md +194 -0
- package/docs/test-isolation.md +100 -0
- package/docs/upgrade.md +159 -0
- package/hooks/_hook-errors.ts +87 -0
- package/hooks/_hook-perf-emit.ts +113 -0
- package/hooks/_hook-timing-ledger.ts +1133 -0
- package/hooks/_nudge-throttle.ts +127 -0
- package/hooks/_recent-blocks.ts +127 -0
- package/hooks/audit-upload.ts +86 -0
- package/hooks/codex-hooks.json +171 -0
- package/hooks/commit-attribution.ts +179 -0
- package/hooks/edit-batching-nudge.ts +154 -0
- package/hooks/genome-scribe-hook.ts +219 -0
- package/hooks/hooks.json +294 -0
- package/hooks/orient-nudge-hook.ts +242 -0
- package/hooks/policy-enforce.ts +214 -0
- package/hooks/post-tool-use-embedding.ts +93 -0
- package/hooks/post-tool-use-genome.sh +37 -0
- package/hooks/post-tool-use-genome.ts +69 -0
- package/hooks/posttooluse-correlate.ts +122 -0
- package/hooks/posttooluse-dedupe-output.ts +241 -0
- package/hooks/posttooluse-genome-refresh.ts +223 -0
- package/hooks/posttooluse-native-nudge.ts +205 -0
- package/hooks/posttooluse-stale-result.ts +277 -0
- package/hooks/pretooluse-bash.ts +63 -0
- package/hooks/pretooluse-budget-guard.ts +318 -0
- package/hooks/pretooluse-common.ts +985 -0
- package/hooks/pretooluse-eco-router.ts +196 -0
- package/hooks/pretooluse-edit.sh +93 -0
- package/hooks/pretooluse-edit.ts +197 -0
- package/hooks/pretooluse-glob.ts +70 -0
- package/hooks/pretooluse-grep.sh +85 -0
- package/hooks/pretooluse-grep.ts +96 -0
- package/hooks/pretooluse-notebookedit.ts +121 -0
- package/hooks/pretooluse-read.sh +118 -0
- package/hooks/pretooluse-read.ts +118 -0
- package/hooks/pretooluse-task.ts +130 -0
- package/hooks/pretooluse-webfetch.ts +66 -0
- package/hooks/pretooluse-websearch.ts +85 -0
- package/hooks/pretooluse-write.ts +128 -0
- package/hooks/pulse-emit.ts +197 -0
- package/hooks/session-end-consolidate.sh +35 -0
- package/hooks/session-end-consolidate.ts +188 -0
- package/hooks/session-end-stats-gc.ts +80 -0
- package/hooks/session-log-append.sh +94 -0
- package/hooks/session-log-append.ts +116 -0
- package/hooks/session-start.ts +993 -0
- package/hooks/sessionend-hook-health-nudge.ts +451 -0
- package/hooks/sessionend-savings-nudge.ts +318 -0
- package/hooks/sessionend-status-line-nudge.ts +296 -0
- package/hooks/sessionstart-brief.ts +140 -0
- package/hooks/sessionstart-cost-refactor.ts +95 -0
- package/hooks/sessionstart-efficient.ts +110 -0
- package/hooks/sessionstart-genome-author.ts +93 -0
- package/hooks/sessionstart-lean-tools.ts +98 -0
- package/hooks/sessionstart-search.ts +97 -0
- package/hooks/stop-accounting.ts +152 -0
- package/hooks/subagent-stop-rollup.ts +162 -0
- package/hooks/userpromptsubmit-brief-trigger.ts +156 -0
- package/package.json +56 -0
- package/ports/README.md +108 -0
- package/ports/cursor/mcp.json +11 -0
- package/ports/goose/recipe.yaml +26 -0
- package/scripts/ashlr-mcp.ts +40 -0
- package/scripts/auto-update.ts +177 -0
- package/scripts/baseline-scan.ts +831 -0
- package/scripts/bench-orchestrate.ts +377 -0
- package/scripts/benchmark-refs.ts +292 -0
- package/scripts/bootstrap.mjs +100 -0
- package/scripts/brief-eval.ts +234 -0
- package/scripts/bump-version.ts +82 -0
- package/scripts/bun-resolve.d.mts +11 -0
- package/scripts/bun-resolve.mjs +59 -0
- package/scripts/calibrate-grep.ts +467 -0
- package/scripts/capture-grep-workload.ts +260 -0
- package/scripts/check-tier.ts +61 -0
- package/scripts/check-version-sync.ts +97 -0
- package/scripts/cli-orchestrate-status.ts +386 -0
- package/scripts/cli-orchestrate.ts +303 -0
- package/scripts/cli.ts +478 -0
- package/scripts/cloud-smoke-test.ts +313 -0
- package/scripts/coach-report.ts +373 -0
- package/scripts/context-db-migrate.ts +137 -0
- package/scripts/context-status.ts +43 -0
- package/scripts/demo-run.ts +255 -0
- package/scripts/deploy-smoke.sh +140 -0
- package/scripts/doctor.ts +992 -0
- package/scripts/embed-file-worker.ts +104 -0
- package/scripts/embed-tune.ts +308 -0
- package/scripts/errors-report.ts +428 -0
- package/scripts/find-test-leak.ts +69 -0
- package/scripts/generate-badge.ts +357 -0
- package/scripts/genome-auto-consolidate.ts +533 -0
- package/scripts/genome-auto-propose.ts +578 -0
- package/scripts/genome-cloud-pull.ts +363 -0
- package/scripts/genome-cloud-push.ts +382 -0
- package/scripts/genome-cloud-sync.ts +587 -0
- package/scripts/genome-commit-watcher.ts +295 -0
- package/scripts/genome-init.ts +986 -0
- package/scripts/genome-key.ts +233 -0
- package/scripts/genome-keygen.ts +168 -0
- package/scripts/genome-link.ts +59 -0
- package/scripts/genome-refresh-worker.ts +364 -0
- package/scripts/genome-rewrap.ts +90 -0
- package/scripts/genome-synthesizer.ts +679 -0
- package/scripts/genome-team-init.ts +312 -0
- package/scripts/handoff-pack.ts +438 -0
- package/scripts/hook-bootstrap.mjs +75 -0
- package/scripts/hook-timing-reader.ts +371 -0
- package/scripts/hook-timings-report.ts +554 -0
- package/scripts/install-genome-hooks.ts +196 -0
- package/scripts/install-onnx-model.ts +154 -0
- package/scripts/install-permissions.ts +276 -0
- package/scripts/install-status-line.ts +150 -0
- package/scripts/mcp-entrypoint.sh +78 -0
- package/scripts/mcp-entrypoint.ts +130 -0
- package/scripts/mdx-angle-bracket-lint.ts +152 -0
- package/scripts/measure-cold-start.ts +126 -0
- package/scripts/migrate-stats-to-sqlite.ts +86 -0
- package/scripts/ollama-setup.ts +498 -0
- package/scripts/onboarding-wizard.ts +1691 -0
- package/scripts/orchestrate-emit-telemetry.ts +177 -0
- package/scripts/orchestrate-expand.ts +517 -0
- package/scripts/orchestrate-run.ts +744 -0
- package/scripts/print-legend.ts +50 -0
- package/scripts/product-metadata.ts +38 -0
- package/scripts/prune-old-memories.ts +208 -0
- package/scripts/publish.sh +88 -0
- package/scripts/read-calibration.ts +122 -0
- package/scripts/report-crash.ts +226 -0
- package/scripts/run-benchmark.ts +750 -0
- package/scripts/run-root-tests.ts +28 -0
- package/scripts/savings-dashboard.ts +1258 -0
- package/scripts/savings-report-extras.ts +447 -0
- package/scripts/savings-status-line.ts +873 -0
- package/scripts/session-event-emit.ts +266 -0
- package/scripts/session-greet.ts +686 -0
- package/scripts/session-log-report.ts +551 -0
- package/scripts/session-resume.ts +614 -0
- package/scripts/set-hook-mode.ts +83 -0
- package/scripts/smoke-cross-platform.ts +117 -0
- package/scripts/smoke-realtime.ts +257 -0
- package/scripts/smoke-tools.ts +21 -0
- package/scripts/stats-cloud-pull.ts +213 -0
- package/scripts/stats-cloud-sync.ts +261 -0
- package/scripts/telemetry-flush.ts +160 -0
- package/scripts/telemetry-status.ts +266 -0
- package/scripts/timing-budget.ts +35 -0
- package/scripts/ui-animation.ts +120 -0
- package/scripts/ui.ts +431 -0
- package/scripts/upgrade-flow.ts +775 -0
- package/scripts/weekly-digest.ts +394 -0
- package/servers/_accounting.ts +78 -0
- package/servers/_ask-router.ts +84 -0
- package/servers/_ast-chunker.ts +434 -0
- package/servers/_ast-helpers.ts +183 -0
- package/servers/_ast-languages.ts +153 -0
- package/servers/_ast-refactor/_shared.ts +221 -0
- package/servers/_ast-refactor/cross-file-rename.ts +560 -0
- package/servers/_ast-refactor/extract-function.ts +357 -0
- package/servers/_ast-refactor/file-local-rename.ts +172 -0
- package/servers/_ast-refactor.ts +41 -0
- package/servers/_ast-skeleton.ts +107 -0
- package/servers/_bash-summarizers-registry.ts +1096 -0
- package/servers/_crash-dump.ts +210 -0
- package/servers/_cwd-clamp.ts +342 -0
- package/servers/_edit-log.ts +28 -0
- package/servers/_edit-match.ts +236 -0
- package/servers/_edit-validate.ts +196 -0
- package/servers/_embed-calibration.ts +123 -0
- package/servers/_embedding-cache.ts +401 -0
- package/servers/_embedding-model.ts +329 -0
- package/servers/_events.ts +113 -0
- package/servers/_genome-cache.ts +154 -0
- package/servers/_genome-commits.ts +172 -0
- package/servers/_genome-crypto-v2.ts +242 -0
- package/servers/_genome-crypto.ts +259 -0
- package/servers/_genome-discoveries.ts +140 -0
- package/servers/_genome-embed-populator.ts +216 -0
- package/servers/_genome-freshness.ts +134 -0
- package/servers/_genome-issues.ts +171 -0
- package/servers/_genome-live.ts +263 -0
- package/servers/_genome-prs.ts +188 -0
- package/servers/_genome-search.ts +479 -0
- package/servers/_genome-sync.ts +297 -0
- package/servers/_history-tracker.ts +279 -0
- package/servers/_http-helpers.ts +151 -0
- package/servers/_identity-hash.ts +151 -0
- package/servers/_llm-providers/anthropic.ts +132 -0
- package/servers/_llm-providers/cloud.ts +195 -0
- package/servers/_llm-providers/index.ts +350 -0
- package/servers/_llm-providers/local.ts +127 -0
- package/servers/_llm-providers/onnx.ts +393 -0
- package/servers/_llm-providers/types.ts +27 -0
- package/servers/_manifest-v2.ts +447 -0
- package/servers/_mcp-host.ts +93 -0
- package/servers/_nudge-events.ts +451 -0
- package/servers/_orchestrate-executor.ts +316 -0
- package/servers/_prefetch.ts +435 -0
- package/servers/_pricing.ts +256 -0
- package/servers/_pro.ts +381 -0
- package/servers/_read-cache.ts +44 -0
- package/servers/_router-handlers.ts +46 -0
- package/servers/_router.ts +55 -0
- package/servers/_run-with-timeout.ts +173 -0
- package/servers/_savings-render.ts +214 -0
- package/servers/_stats-sqlite.ts +640 -0
- package/servers/_stats.ts +1183 -0
- package/servers/_status-line-cells.ts +469 -0
- package/servers/_streaks.ts +162 -0
- package/servers/_summarize.ts +208 -0
- package/servers/_task-graph-render.ts +217 -0
- package/servers/_task-graph.ts +545 -0
- package/servers/_telemetry.ts +719 -0
- package/servers/_test-parsers.ts +530 -0
- package/servers/_test-watch.ts +626 -0
- package/servers/_text-helpers.ts +37 -0
- package/servers/_token-measure.ts +160 -0
- package/servers/_tool-base.ts +248 -0
- package/servers/_weekly-digest.ts +391 -0
- package/servers/ask-server-handlers.ts +49 -0
- package/servers/ask-server.ts +174 -0
- package/servers/bash-server-handlers.ts +138 -0
- package/servers/bash-server.ts +1136 -0
- package/servers/bench.ts +186 -0
- package/servers/diff-semantic-server-handlers.ts +39 -0
- package/servers/diff-semantic-server.ts +566 -0
- package/servers/diff-server-handlers.ts +317 -0
- package/servers/diff-server.ts +20 -0
- package/servers/edit-server-handlers.ts +31 -0
- package/servers/edit-server.ts +209 -0
- package/servers/edit-structural-server-handlers.ts +322 -0
- package/servers/efficiency-server-handlers.ts +161 -0
- package/servers/efficiency-server.ts +1135 -0
- package/servers/flush-server-handlers.ts +21 -0
- package/servers/flush-server.ts +19 -0
- package/servers/genome-server-handlers.ts +118 -0
- package/servers/genome-server.ts +397 -0
- package/servers/github-server-handlers.ts +187 -0
- package/servers/github-server.ts +856 -0
- package/servers/glob-server-handlers.ts +233 -0
- package/servers/glob-server.ts +20 -0
- package/servers/grep-server-handlers.ts +61 -0
- package/servers/grep-server.ts +564 -0
- package/servers/http-server-handlers.ts +36 -0
- package/servers/http-server.ts +149 -0
- package/servers/logs-server-handlers.ts +42 -0
- package/servers/logs-server.ts +359 -0
- package/servers/ls-server-handlers.ts +166 -0
- package/servers/ls-server.ts +20 -0
- package/servers/multi-edit-server-handlers.ts +51 -0
- package/servers/multi-edit-server.ts +422 -0
- package/servers/notebook-edit-server-handlers.ts +34 -0
- package/servers/notebook-edit-server.ts +181 -0
- package/servers/orient-server-handlers.ts +52 -0
- package/servers/orient-server.ts +436 -0
- package/servers/pipe-server-handlers.ts +68 -0
- package/servers/pipe-server.ts +300 -0
- package/servers/read-server-handlers.ts +42 -0
- package/servers/read-server.ts +206 -0
- package/servers/rename-file-server-handlers.ts +67 -0
- package/servers/rename-file-server.ts +699 -0
- package/servers/savings-server-handlers.ts +27 -0
- package/servers/savings-server.ts +54 -0
- package/servers/search-replace-regex-server-handlers.ts +96 -0
- package/servers/search-replace-regex-server.ts +751 -0
- package/servers/sql-server-handlers.ts +58 -0
- package/servers/sql-server.ts +505 -0
- package/servers/task-server-handlers.ts +358 -0
- package/servers/task-server.ts +24 -0
- package/servers/test-server-handlers.ts +351 -0
- package/servers/test-server.ts +20 -0
- package/servers/tree-server-handlers.ts +503 -0
- package/servers/tree-server.ts +20 -0
- package/servers/webfetch-server-handlers.ts +166 -0
- package/servers/webfetch-server.ts +20 -0
- package/servers/websearch-server-handlers.ts +215 -0
- package/servers/websearch-server.ts +19 -0
- package/servers/write-server-handlers.ts +27 -0
- package/servers/write-server.ts +97 -0
- package/skills/ashlr-brief/SKILL.md +80 -0
- package/skills/ashlr-codex-doctor/SKILL.md +21 -0
- package/skills/ashlr-codex-efficient-tools/SKILL.md +17 -0
- package/skills/ashlr-codex-genome-refresh/SKILL.md +20 -0
- package/skills/ashlr-codex-parallel-explore/SKILL.md +20 -0
- package/skills/ashlr-codex-savings/SKILL.md +17 -0
- package/skills/ashlr-cost-refactor/SKILL.md +50 -0
- package/skills/ashlr-efficient/SKILL.md +64 -0
- package/skills/ashlr-genome-author/SKILL.md +43 -0
- package/skills/ashlr-lean-tools/SKILL.md +43 -0
- package/skills/ashlr-search/SKILL.md +46 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, deterministic status-line renderer — structured output.
|
|
3
|
+
*
|
|
4
|
+
* This module is the single source of truth for every visual decision the
|
|
5
|
+
* animated status line makes. It returns `Cell[]` (a char + optional color +
|
|
6
|
+
* optional bold) instead of ANSI-escaped strings, so two consumers can share
|
|
7
|
+
* exactly the same animation logic:
|
|
8
|
+
*
|
|
9
|
+
* 1. `scripts/ui-animation.ts` — the CLI wrapper. Calls the cell producers
|
|
10
|
+
* here, then serializes Cell[] into ANSI escapes for the terminal.
|
|
11
|
+
* 2. `video/src/compositions/StatusLineStill.tsx` — the Remotion port.
|
|
12
|
+
* Calls the same cell producers, then renders each cell as a `<span>`
|
|
13
|
+
* with inline RGB styles for the hero video.
|
|
14
|
+
*
|
|
15
|
+
* Zero Node deps, zero I/O, zero `Date` calls. Every function is
|
|
16
|
+
* `(values, frame, msSinceActive, capability) -> something deterministic`.
|
|
17
|
+
* Test golden-frames without mocking the clock.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Types
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
export interface RGB {
|
|
25
|
+
r: number;
|
|
26
|
+
g: number;
|
|
27
|
+
b: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface Cell {
|
|
31
|
+
/** Visible character (single grapheme). */
|
|
32
|
+
char: string;
|
|
33
|
+
/** Foreground RGB. Absent means "use terminal default / inherited color". */
|
|
34
|
+
fg?: RGB;
|
|
35
|
+
/** Bold styling. */
|
|
36
|
+
bold?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* True when this cell is part of an active pulse/activity effect. Downstream
|
|
39
|
+
* renderers (especially the Remotion port) can use this to emit extra
|
|
40
|
+
* animation hints like a glow filter.
|
|
41
|
+
*/
|
|
42
|
+
activity?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface Capability {
|
|
46
|
+
/** Terminal supports 24-bit color escapes. */
|
|
47
|
+
truecolor: boolean;
|
|
48
|
+
/** Terminal supports Unicode. False falls through to ASCII ramps. */
|
|
49
|
+
unicode: boolean;
|
|
50
|
+
/** Master animation switch. False emits a single static frame. */
|
|
51
|
+
animate: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Capability detection
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
export function detectCapability(env: NodeJS.ProcessEnv = process.env): Capability {
|
|
59
|
+
const noColor = truthyEnv(env.NO_COLOR);
|
|
60
|
+
const animateOff = env.ASHLR_STATUS_ANIMATE === "0";
|
|
61
|
+
const forceAnimate = env.ASHLR_STATUS_ANIMATE === "1";
|
|
62
|
+
const colorterm = (env.COLORTERM ?? "").toLowerCase();
|
|
63
|
+
const term = (env.TERM ?? "").toLowerCase();
|
|
64
|
+
const truecolor = !noColor && (colorterm === "truecolor" || colorterm === "24bit" || forceAnimate);
|
|
65
|
+
const lang = (env.LANG ?? env.LC_ALL ?? env.LC_CTYPE ?? "").toLowerCase();
|
|
66
|
+
const unicode = lang.includes("utf") || term.includes("xterm") || term.includes("256color") || truecolor;
|
|
67
|
+
const animate = !animateOff && (truecolor || forceAnimate);
|
|
68
|
+
return { truecolor, unicode, animate };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function truthyEnv(v: string | undefined): boolean {
|
|
72
|
+
if (v == null) return false;
|
|
73
|
+
const t = v.trim().toLowerCase();
|
|
74
|
+
return t !== "" && t !== "0" && t !== "false" && t !== "no";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Frame clock
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
export const FRAME_MS = 120;
|
|
82
|
+
|
|
83
|
+
/** Integer frame index for a given wall-clock ms. */
|
|
84
|
+
export function frameAt(nowMs: number, frameMs: number = FRAME_MS): number {
|
|
85
|
+
return Math.floor(nowMs / frameMs);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Sparkline glyph ramps
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
export const UNICODE_RAMP: readonly string[] = [
|
|
93
|
+
"\u2800",
|
|
94
|
+
"\u2840",
|
|
95
|
+
"\u2844",
|
|
96
|
+
"\u2846",
|
|
97
|
+
"\u2847",
|
|
98
|
+
"\u28E7",
|
|
99
|
+
"\u28F7",
|
|
100
|
+
"\u28FF",
|
|
101
|
+
"\u2581",
|
|
102
|
+
"\u2582",
|
|
103
|
+
"\u2583",
|
|
104
|
+
"\u2584",
|
|
105
|
+
"\u2585",
|
|
106
|
+
"\u2586",
|
|
107
|
+
"\u2587",
|
|
108
|
+
"\u2588",
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
export const ASCII_RAMP: readonly string[] = [" ", ".", ":", "|", "#"];
|
|
112
|
+
|
|
113
|
+
function pickRamp(cap: Capability): readonly string[] {
|
|
114
|
+
return cap.unicode ? UNICODE_RAMP : ASCII_RAMP;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function valuesToRamp(values: readonly number[], rampLen: number): number[] {
|
|
118
|
+
const max = Math.max(...values, 0);
|
|
119
|
+
if (max <= 0) return values.map(() => 0);
|
|
120
|
+
return values.map((v) => {
|
|
121
|
+
if (v <= 0) return 0;
|
|
122
|
+
const last = rampLen - 1;
|
|
123
|
+
return Math.max(1, Math.min(last, Math.ceil((v / max) * last)));
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// Color helpers
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
export const BRAND_DARK: RGB = { r: 0, g: 208, b: 156 }; // #00d09c
|
|
132
|
+
export const BRAND_LIGHT: RGB = { r: 124, g: 255, b: 214 }; // #7cffd6
|
|
133
|
+
export const PULSE_CELL: RGB = { r: 255, g: 255, b: 255 };
|
|
134
|
+
|
|
135
|
+
function lerp(a: number, b: number, t: number): number {
|
|
136
|
+
return Math.round(a + (b - a) * Math.max(0, Math.min(1, t)));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function lerpColor(a: RGB, b: RGB, t: number): RGB {
|
|
140
|
+
return { r: lerp(a.r, b.r, t), g: lerp(a.g, b.g, t), b: lerp(a.b, b.b, t) };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Gradient sweep + activity pulse math
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
export function gradientTs(width: number, frame: number): number[] {
|
|
148
|
+
if (width <= 0) return [];
|
|
149
|
+
if (width === 1) return [0];
|
|
150
|
+
const ts: number[] = [];
|
|
151
|
+
const phase = ((frame % (width * 2)) + width * 2) % (width * 2);
|
|
152
|
+
for (let i = 0; i < width; i++) {
|
|
153
|
+
const shifted = (i + phase) % (width * 2);
|
|
154
|
+
const raw = shifted < width ? shifted / (width - 1) : (width * 2 - 1 - shifted) / (width - 1);
|
|
155
|
+
ts.push(raw);
|
|
156
|
+
}
|
|
157
|
+
return ts;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface PulseState {
|
|
161
|
+
/** Linear position 0..1 along the sparkline (wraps). */
|
|
162
|
+
position: number;
|
|
163
|
+
/** Intensity 0..1 used to blend the pulse color over the base cell. */
|
|
164
|
+
intensity: number;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function computePulse(frame: number, msSinceActive: number, width: number): PulseState {
|
|
168
|
+
if (!Number.isFinite(msSinceActive) || msSinceActive < 0 || width <= 0) {
|
|
169
|
+
return { position: 0, intensity: 0 };
|
|
170
|
+
}
|
|
171
|
+
const ACTIVE_MS = 4_000;
|
|
172
|
+
const FADE_MS = 500;
|
|
173
|
+
let intensity: number;
|
|
174
|
+
if (msSinceActive <= ACTIVE_MS) intensity = 1;
|
|
175
|
+
else if (msSinceActive <= ACTIVE_MS + FADE_MS) {
|
|
176
|
+
intensity = 1 - (msSinceActive - ACTIVE_MS) / FADE_MS;
|
|
177
|
+
} else intensity = 0;
|
|
178
|
+
const position = ((frame % (width * 3)) + width * 3) % (width * 3) / (width * 3);
|
|
179
|
+
return { position, intensity };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function sweepFactor(cellIndex: number, headCell: number, width: number): number {
|
|
183
|
+
const w = Math.max(1, width);
|
|
184
|
+
const delta = ((cellIndex - headCell) % w + w) % w;
|
|
185
|
+
if (delta === 0) return 1.0;
|
|
186
|
+
if (delta === w - 1) return 0.45;
|
|
187
|
+
if (delta === w - 2) return 0.15;
|
|
188
|
+
return 0.0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Heartbeat glyph
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
export const HEARTBEAT_FRAMES = [
|
|
196
|
+
"\u2840", "\u2844", "\u2846", "\u2847", "\u28C7",
|
|
197
|
+
"\u28E7", "\u28F7", "\u28FF", "\u28F7", "\u28E7",
|
|
198
|
+
"\u28C7", "\u2847", "\u2846", "\u2844", "\u2840",
|
|
199
|
+
] as const;
|
|
200
|
+
export const HEARTBEAT_IDLE = "\u00B7";
|
|
201
|
+
export const HEARTBEAT_ASCII_IDLE = ".";
|
|
202
|
+
export const HEARTBEAT_ASCII_ACTIVE = ["-", "=", "*", "=", "-"] as const;
|
|
203
|
+
|
|
204
|
+
const HEARTBEAT_IDLE_COLOR: RGB = { r: 100, g: 110, b: 120 };
|
|
205
|
+
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// Context pressure widget
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
export const CTX_GREEN: RGB = { r: 0, g: 160, b: 120 };
|
|
211
|
+
export const CTX_YELLOW: RGB = { r: 212, g: 167, b: 44 };
|
|
212
|
+
export const CTX_ORANGE: RGB = { r: 217, g: 121, b: 58 };
|
|
213
|
+
export const CTX_RED: RGB = { r: 225, g: 91, b: 91 };
|
|
214
|
+
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// Activity indicator
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
export const ACTIVITY_ACTIVE_MS = 4_000;
|
|
220
|
+
export const ACTIVITY_GLYPH_UNICODE = "\u2191";
|
|
221
|
+
export const ACTIVITY_GLYPH_ASCII = "+";
|
|
222
|
+
|
|
223
|
+
// ===========================================================================
|
|
224
|
+
// Cell producers — the public API the CLI and Remotion renderers both consume.
|
|
225
|
+
// ===========================================================================
|
|
226
|
+
|
|
227
|
+
export interface SparklineInput {
|
|
228
|
+
values: readonly number[];
|
|
229
|
+
frame: number;
|
|
230
|
+
msSinceActive: number;
|
|
231
|
+
cap: Capability;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Build the sparkline as structured cells.
|
|
236
|
+
*
|
|
237
|
+
* When `cap.animate` or `cap.truecolor` is false, each cell has no `fg` (plain
|
|
238
|
+
* terminal color) or a single brand-green `fg` depending on caller intent. In
|
|
239
|
+
* the full animated case, each cell gets a gradient-interpolated base color
|
|
240
|
+
* plus optional pulse overlay.
|
|
241
|
+
*/
|
|
242
|
+
export function sparklineCells({ values, frame, msSinceActive, cap }: SparklineInput): Cell[] {
|
|
243
|
+
const ramp = pickRamp(cap);
|
|
244
|
+
const idxs = valuesToRamp(values, ramp.length);
|
|
245
|
+
const chars = idxs.map((i) => ramp[i]!);
|
|
246
|
+
|
|
247
|
+
if (!cap.animate || !cap.truecolor) {
|
|
248
|
+
return chars.map((char) => ({ char }));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const ts = gradientTs(chars.length, frame);
|
|
252
|
+
const pulse = computePulse(frame, msSinceActive, chars.length);
|
|
253
|
+
const headCell = pulse.intensity > 0
|
|
254
|
+
? Math.floor(pulse.position * chars.length) % chars.length
|
|
255
|
+
: -1;
|
|
256
|
+
|
|
257
|
+
return chars.map((char, i) => {
|
|
258
|
+
const base = lerpColor(BRAND_DARK, BRAND_LIGHT, ts[i] ?? 0);
|
|
259
|
+
let color = base;
|
|
260
|
+
let activity = false;
|
|
261
|
+
if (headCell >= 0) {
|
|
262
|
+
const factor = sweepFactor(i, headCell, chars.length) * pulse.intensity;
|
|
263
|
+
if (factor > 0) {
|
|
264
|
+
color = lerpColor(base, PULSE_CELL, factor);
|
|
265
|
+
activity = true;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const cell: Cell = { char, fg: color };
|
|
269
|
+
if (activity) cell.activity = true;
|
|
270
|
+
return cell;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Single-cell heartbeat glyph (goes between "ashlr" and the sparkline).
|
|
276
|
+
*/
|
|
277
|
+
export function heartbeatCell(frame: number, msSinceActive: number, cap: Capability): Cell {
|
|
278
|
+
const active = msSinceActive <= 4_500;
|
|
279
|
+
if (!cap.animate) {
|
|
280
|
+
return { char: cap.unicode ? HEARTBEAT_IDLE : HEARTBEAT_ASCII_IDLE };
|
|
281
|
+
}
|
|
282
|
+
if (!active) {
|
|
283
|
+
const idle = cap.unicode ? HEARTBEAT_IDLE : HEARTBEAT_ASCII_IDLE;
|
|
284
|
+
return cap.truecolor
|
|
285
|
+
? { char: idle, fg: HEARTBEAT_IDLE_COLOR }
|
|
286
|
+
: { char: idle };
|
|
287
|
+
}
|
|
288
|
+
const frames = cap.unicode ? HEARTBEAT_FRAMES : HEARTBEAT_ASCII_ACTIVE;
|
|
289
|
+
const ch = frames[((frame % frames.length) + frames.length) % frames.length]!;
|
|
290
|
+
if (!cap.truecolor) return { char: ch, activity: true };
|
|
291
|
+
|
|
292
|
+
const ACTIVE_MS = 4_000;
|
|
293
|
+
const FADE_MS = 500;
|
|
294
|
+
const t = msSinceActive <= ACTIVE_MS
|
|
295
|
+
? 0
|
|
296
|
+
: Math.min(1, (msSinceActive - ACTIVE_MS) / FADE_MS);
|
|
297
|
+
const color = lerpColor(BRAND_LIGHT, BRAND_DARK, t);
|
|
298
|
+
return { char: ch, fg: color, activity: true };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Activity indicator next to the session counter. Returns `null` when idle so
|
|
303
|
+
* the caller can omit the cell entirely (width stays stable — idle = 0 cells,
|
|
304
|
+
* active = 1 cell).
|
|
305
|
+
*/
|
|
306
|
+
export function activityCell(msSinceActive: number, cap: Capability): Cell | null {
|
|
307
|
+
if (!Number.isFinite(msSinceActive) || msSinceActive > ACTIVITY_ACTIVE_MS) return null;
|
|
308
|
+
const glyph = cap.unicode ? ACTIVITY_GLYPH_UNICODE : ACTIVITY_GLYPH_ASCII;
|
|
309
|
+
if (!cap.truecolor) return { char: glyph, activity: true };
|
|
310
|
+
const t = msSinceActive / ACTIVITY_ACTIVE_MS;
|
|
311
|
+
const color = lerpColor(BRAND_LIGHT, BRAND_DARK, t);
|
|
312
|
+
return { char: glyph, fg: color, activity: true };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Context-pressure micro-widget, rendered as a sequence of cells spelling out
|
|
317
|
+
* e.g. `ctx: 72%`. Width is 8–9 visible cells depending on the percentage.
|
|
318
|
+
*
|
|
319
|
+
* When `cap.truecolor` is false every cell is returned without `fg` so the
|
|
320
|
+
* caller renders in the terminal's default color.
|
|
321
|
+
*/
|
|
322
|
+
export function contextPressureCells(pct: number, cap: Capability): Cell[] {
|
|
323
|
+
const label = `ctx: ${Math.round(pct)}%`;
|
|
324
|
+
if (!cap.truecolor) return [...label].map((char) => ({ char }));
|
|
325
|
+
|
|
326
|
+
let color: RGB;
|
|
327
|
+
let bold = false;
|
|
328
|
+
if (pct >= 95) {
|
|
329
|
+
color = CTX_RED;
|
|
330
|
+
bold = true;
|
|
331
|
+
} else if (pct >= 80) {
|
|
332
|
+
color = CTX_ORANGE;
|
|
333
|
+
} else if (pct >= 60) {
|
|
334
|
+
color = CTX_YELLOW;
|
|
335
|
+
} else {
|
|
336
|
+
color = CTX_GREEN;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return [...label].map((char) => {
|
|
340
|
+
const cell: Cell = { char, fg: color };
|
|
341
|
+
if (bold) cell.bold = true;
|
|
342
|
+
return cell;
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ---------------------------------------------------------------------------
|
|
347
|
+
// Budget segment — shows "$X / $Y · Z%" or "Nt / Nmax · Z%"
|
|
348
|
+
// ---------------------------------------------------------------------------
|
|
349
|
+
|
|
350
|
+
export const BUDGET_OK: RGB = { r: 0, g: 160, b: 120 }; // green
|
|
351
|
+
export const BUDGET_WARN: RGB = { r: 212, g: 167, b: 44 }; // yellow
|
|
352
|
+
export const BUDGET_CRIT: RGB = { r: 225, g: 91, b: 91 }; // red
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Return cells for the budget segment, or an empty array when no budget is set.
|
|
356
|
+
*
|
|
357
|
+
* When `ASHLR_SESSION_BUDGET_USD` is set renders: `$used / $cap · Z%`
|
|
358
|
+
* When `ASHLR_SESSION_BUDGET_TOKENS` is set renders: `Nt / Nmax · Z%`
|
|
359
|
+
*/
|
|
360
|
+
export function budgetCells(
|
|
361
|
+
usedUsd: number,
|
|
362
|
+
budgetUsd: number,
|
|
363
|
+
usedTokens: number,
|
|
364
|
+
budgetTokens: number,
|
|
365
|
+
cap: Capability,
|
|
366
|
+
): Cell[] {
|
|
367
|
+
if (budgetUsd <= 0 && budgetTokens <= 0) return [];
|
|
368
|
+
|
|
369
|
+
let label: string;
|
|
370
|
+
let pct: number;
|
|
371
|
+
|
|
372
|
+
if (budgetUsd > 0) {
|
|
373
|
+
pct = budgetUsd > 0 ? usedUsd / budgetUsd : 0;
|
|
374
|
+
label = `$${usedUsd.toFixed(2)} / $${budgetUsd.toFixed(2)} · ${Math.round(pct * 100)}%`;
|
|
375
|
+
} else {
|
|
376
|
+
pct = budgetTokens > 0 ? usedTokens / budgetTokens : 0;
|
|
377
|
+
const usedK = usedTokens >= 1000 ? `${Math.round(usedTokens / 1000)}k` : String(usedTokens);
|
|
378
|
+
const maxK = budgetTokens >= 1000 ? `${Math.round(budgetTokens / 1000)}k` : String(budgetTokens);
|
|
379
|
+
label = `${usedK} / ${maxK} · ${Math.round(pct * 100)}%`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (!cap.truecolor) return [...label].map((char) => ({ char }));
|
|
383
|
+
|
|
384
|
+
let color: RGB;
|
|
385
|
+
let bold = false;
|
|
386
|
+
if (pct >= 1.0) {
|
|
387
|
+
color = BUDGET_CRIT;
|
|
388
|
+
bold = true;
|
|
389
|
+
} else if (pct >= 0.95) {
|
|
390
|
+
color = BUDGET_CRIT;
|
|
391
|
+
} else if (pct >= 0.80) {
|
|
392
|
+
color = BUDGET_WARN;
|
|
393
|
+
} else {
|
|
394
|
+
color = BUDGET_OK;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return [...label].map((char) => {
|
|
398
|
+
const cell: Cell = { char, fg: color };
|
|
399
|
+
if (bold) cell.bold = true;
|
|
400
|
+
return cell;
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
// Eco badge — shows "eco" when ASHLR_ECO=1
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
|
|
408
|
+
export const ECO_COLOR: RGB = { r: 80, g: 200, b: 100 }; // leaf green
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Return cells for the eco badge, or empty array when eco mode is off.
|
|
412
|
+
* Reads ASHLR_ECO from env unless overridden.
|
|
413
|
+
*/
|
|
414
|
+
export function ecoBadgeCells(
|
|
415
|
+
cap: Capability,
|
|
416
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
417
|
+
): Cell[] {
|
|
418
|
+
if (!env.ASHLR_ECO || env.ASHLR_ECO === "0") return [];
|
|
419
|
+
const label = "eco";
|
|
420
|
+
if (!cap.truecolor) return [...label].map((char) => ({ char }));
|
|
421
|
+
return [...label].map((char) => ({ char, fg: ECO_COLOR }));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ===========================================================================
|
|
425
|
+
// Cell → ANSI serialization (used by the CLI wrapper)
|
|
426
|
+
// ===========================================================================
|
|
427
|
+
|
|
428
|
+
const ANSI_RESET = "\x1b[0m";
|
|
429
|
+
|
|
430
|
+
function ansiFg(c: RGB): string {
|
|
431
|
+
return `\x1b[38;2;${c.r};${c.g};${c.b}m`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Collapse a `Cell[]` into an ANSI-escaped string suitable for TTY output.
|
|
436
|
+
* Runs of cells that share `(fg, bold)` share a single escape prefix so the
|
|
437
|
+
* output stays compact and readable in a `cat`'d log.
|
|
438
|
+
*
|
|
439
|
+
* Callers who want no ANSI (raw character dump for width measurement) can just
|
|
440
|
+
* join `cells.map(c => c.char)` directly.
|
|
441
|
+
*/
|
|
442
|
+
export function cellsToAnsi(cells: readonly Cell[]): string {
|
|
443
|
+
if (cells.length === 0) return "";
|
|
444
|
+
const parts: string[] = [];
|
|
445
|
+
let last: { fg?: RGB; bold?: boolean } = {};
|
|
446
|
+
let hadStyle = false;
|
|
447
|
+
|
|
448
|
+
for (const cell of cells) {
|
|
449
|
+
const sameFg =
|
|
450
|
+
(cell.fg == null && last.fg == null) ||
|
|
451
|
+
(cell.fg != null && last.fg != null &&
|
|
452
|
+
cell.fg.r === last.fg.r && cell.fg.g === last.fg.g && cell.fg.b === last.fg.b);
|
|
453
|
+
const sameBold = !!cell.bold === !!last.bold;
|
|
454
|
+
if (!(sameFg && sameBold)) {
|
|
455
|
+
if (hadStyle) parts.push(ANSI_RESET);
|
|
456
|
+
if (cell.bold) parts.push("\x1b[1m");
|
|
457
|
+
if (cell.fg) {
|
|
458
|
+
parts.push(ansiFg(cell.fg));
|
|
459
|
+
hadStyle = true;
|
|
460
|
+
} else {
|
|
461
|
+
hadStyle = !!cell.bold;
|
|
462
|
+
}
|
|
463
|
+
last = { fg: cell.fg, bold: cell.bold };
|
|
464
|
+
}
|
|
465
|
+
parts.push(cell.char);
|
|
466
|
+
}
|
|
467
|
+
if (hadStyle) parts.push(ANSI_RESET);
|
|
468
|
+
return parts.join("");
|
|
469
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _streaks.ts — Saving-streak tracking for the ashlr-plugin.
|
|
3
|
+
*
|
|
4
|
+
* Persists to ~/.ashlr/streaks.json:
|
|
5
|
+
* { currentStreak: N, longestStreak: M, lastActiveDay: "YYYY-MM-DD" }
|
|
6
|
+
*
|
|
7
|
+
* Called by recordSaving (gated to once-per-day to avoid hot-path overhead).
|
|
8
|
+
* Read by the status-line, dashboard, and savings-report surfaces.
|
|
9
|
+
*
|
|
10
|
+
* Grace rule: a 1-day gap does NOT break the streak (life happens).
|
|
11
|
+
* A 2+ day gap resets currentStreak to 1.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
15
|
+
import { homedir } from "os";
|
|
16
|
+
import { join } from "path";
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Types
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
export interface StreakData {
|
|
23
|
+
currentStreak: number;
|
|
24
|
+
longestStreak: number;
|
|
25
|
+
lastActiveDay: string; // YYYY-MM-DD UTC
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Paths
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
export function streakPath(home: string = homedir()): string {
|
|
33
|
+
return join(home, ".ashlr", "streaks.json");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// IO helpers
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
export function readStreaks(home: string = homedir()): StreakData {
|
|
41
|
+
try {
|
|
42
|
+
const p = streakPath(home);
|
|
43
|
+
if (!existsSync(p)) return { currentStreak: 0, longestStreak: 0, lastActiveDay: "" };
|
|
44
|
+
const raw = JSON.parse(readFileSync(p, "utf-8")) as Partial<StreakData>;
|
|
45
|
+
return {
|
|
46
|
+
currentStreak: typeof raw.currentStreak === "number" ? raw.currentStreak : 0,
|
|
47
|
+
longestStreak: typeof raw.longestStreak === "number" ? raw.longestStreak : 0,
|
|
48
|
+
lastActiveDay: typeof raw.lastActiveDay === "string" ? raw.lastActiveDay : "",
|
|
49
|
+
};
|
|
50
|
+
} catch {
|
|
51
|
+
return { currentStreak: 0, longestStreak: 0, lastActiveDay: "" };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function writeStreaks(data: StreakData, home: string = homedir()): void {
|
|
56
|
+
try {
|
|
57
|
+
mkdirSync(join(home, ".ashlr"), { recursive: true });
|
|
58
|
+
writeFileSync(streakPath(home), JSON.stringify(data, null, 2));
|
|
59
|
+
} catch {
|
|
60
|
+
/* best-effort */
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Core logic
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
/** UTC YYYY-MM-DD for the given timestamp (or now). */
|
|
69
|
+
export function todayKey(nowMs: number = Date.now()): string {
|
|
70
|
+
return new Date(nowMs).toISOString().slice(0, 10);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Number of calendar days between two YYYY-MM-DD strings. */
|
|
74
|
+
export function daysBetween(a: string, b: string): number {
|
|
75
|
+
const msA = Date.parse(a);
|
|
76
|
+
const msB = Date.parse(b);
|
|
77
|
+
if (!Number.isFinite(msA) || !Number.isFinite(msB)) return Infinity;
|
|
78
|
+
return Math.round(Math.abs(msA - msB) / 86_400_000);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Compute updated streak given existing data and today's key.
|
|
83
|
+
*
|
|
84
|
+
* Rules:
|
|
85
|
+
* - same day as lastActiveDay → no change (idempotent)
|
|
86
|
+
* - gap of 1 day (yesterday was the last active day) → streak extends
|
|
87
|
+
* - gap of 2+ days BUT ≤ grace (1 missed day) → still extends
|
|
88
|
+
* - gap > 1-day grace → reset to 1
|
|
89
|
+
*
|
|
90
|
+
* Grace rule: a SINGLE missed day (gap = 2: last active 2 days ago) is
|
|
91
|
+
* forgiven. Gap of 3+ days breaks the streak.
|
|
92
|
+
*/
|
|
93
|
+
export function computeNewStreak(data: StreakData, today: string): StreakData {
|
|
94
|
+
if (!today) return data;
|
|
95
|
+
|
|
96
|
+
// No prior active day → start streak.
|
|
97
|
+
if (!data.lastActiveDay) {
|
|
98
|
+
return {
|
|
99
|
+
currentStreak: 1,
|
|
100
|
+
longestStreak: Math.max(1, data.longestStreak),
|
|
101
|
+
lastActiveDay: today,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const gap = daysBetween(data.lastActiveDay, today);
|
|
106
|
+
|
|
107
|
+
// Same day — idempotent.
|
|
108
|
+
if (gap === 0) return data;
|
|
109
|
+
|
|
110
|
+
let newStreak: number;
|
|
111
|
+
if (gap <= 2) {
|
|
112
|
+
// 1-day gap = consecutive; 2-day gap = one missed day (grace).
|
|
113
|
+
newStreak = data.currentStreak + 1;
|
|
114
|
+
} else {
|
|
115
|
+
// Streak broken.
|
|
116
|
+
newStreak = 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
currentStreak: newStreak,
|
|
121
|
+
longestStreak: Math.max(newStreak, data.longestStreak),
|
|
122
|
+
lastActiveDay: today,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Public API: bump the streak for today.
|
|
128
|
+
* Gated by lastActiveDay so it's a no-op if already called today.
|
|
129
|
+
* Never throws.
|
|
130
|
+
*/
|
|
131
|
+
export function bumpStreak(home: string = homedir(), nowMs: number = Date.now()): void {
|
|
132
|
+
try {
|
|
133
|
+
const data = readStreaks(home);
|
|
134
|
+
const today = todayKey(nowMs);
|
|
135
|
+
// If we already recorded today, skip the write entirely.
|
|
136
|
+
if (data.lastActiveDay === today) return;
|
|
137
|
+
const updated = computeNewStreak(data, today);
|
|
138
|
+
writeStreaks(updated, home);
|
|
139
|
+
} catch {
|
|
140
|
+
/* best-effort */
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// Rendering helpers (used by status-line, dashboard, savings)
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
/** Returns a streak label like "5d streak" or "" when streak < 3. */
|
|
149
|
+
export function renderStreakLabel(data: StreakData): string {
|
|
150
|
+
if (data.currentStreak < 3) return "";
|
|
151
|
+
return `${data.currentStreak}d streak`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Returns a streak badge for the status line: "* Nd streak" (ASCII star,
|
|
156
|
+
* no emoji — matches the no-emoji convention of the rest of the status line).
|
|
157
|
+
* Returns "" when streak < 3.
|
|
158
|
+
*/
|
|
159
|
+
export function renderStreakBadge(data: StreakData): string {
|
|
160
|
+
if (data.currentStreak < 3) return "";
|
|
161
|
+
return `* ${data.currentStreak}d streak`;
|
|
162
|
+
}
|