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,686 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* ashlr session-start greeting — renders the human-facing welcome on each
|
|
4
|
+
* Claude Code session.
|
|
5
|
+
*
|
|
6
|
+
* Three personalities:
|
|
7
|
+
* 1. First-run (no ~/.ashlr/session-state.json) → welcome box, optional
|
|
8
|
+
* Ollama detection, next-steps checklist.
|
|
9
|
+
* 2. Normal run → 1-line "last session saved X tok ($Y). Lifetime: …".
|
|
10
|
+
* 3. Weekly run (>=7 days since last weekly, or runCount % 20 === 0) →
|
|
11
|
+
* compact 7-day sparkline + totals + top tool, points at
|
|
12
|
+
* /ashlr:ashlr-dashboard for the full view.
|
|
13
|
+
*
|
|
14
|
+
* Everything is written to stderr so it surfaces in the Claude Code
|
|
15
|
+
* transcript without polluting hook JSON on stdout.
|
|
16
|
+
*
|
|
17
|
+
* Respect:
|
|
18
|
+
* - `ASHLR_QUIET=1` — skip all output (still updates state).
|
|
19
|
+
* - `ASHLR_NO_GREET=1` — skip greeting but keep state updates.
|
|
20
|
+
* - `NO_COLOR=1` — no ANSI.
|
|
21
|
+
* - Non-TTY stderr → plain text, no boxes.
|
|
22
|
+
*
|
|
23
|
+
* Contract: never throws, never exits non-zero. Corrupt state file resets as
|
|
24
|
+
* if first-run.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
28
|
+
import { homedir } from "os";
|
|
29
|
+
import { dirname, join } from "path";
|
|
30
|
+
import { spawnSync } from "child_process";
|
|
31
|
+
import { buildDigestBanner, priorWeekKey, weekDayKeys } from "../servers/_weekly-digest.ts";
|
|
32
|
+
import { readStreaks } from "../servers/_streaks.ts";
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Types
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
interface PerTool {
|
|
39
|
+
calls?: number;
|
|
40
|
+
tokensSaved?: number;
|
|
41
|
+
}
|
|
42
|
+
interface ByTool {
|
|
43
|
+
[k: string]: PerTool | undefined;
|
|
44
|
+
}
|
|
45
|
+
interface PerDay {
|
|
46
|
+
calls?: number;
|
|
47
|
+
tokensSaved?: number;
|
|
48
|
+
}
|
|
49
|
+
interface ByDay {
|
|
50
|
+
[date: string]: PerDay | undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface SessionStats {
|
|
54
|
+
startedAt?: string;
|
|
55
|
+
calls?: number;
|
|
56
|
+
tokensSaved?: number;
|
|
57
|
+
byTool?: ByTool;
|
|
58
|
+
}
|
|
59
|
+
interface LifetimeStats {
|
|
60
|
+
calls?: number;
|
|
61
|
+
tokensSaved?: number;
|
|
62
|
+
byTool?: ByTool;
|
|
63
|
+
byDay?: ByDay;
|
|
64
|
+
}
|
|
65
|
+
interface Stats {
|
|
66
|
+
session?: SessionStats;
|
|
67
|
+
lifetime?: LifetimeStats;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface SessionState {
|
|
71
|
+
firstRunAt: string;
|
|
72
|
+
lastRunAt: string;
|
|
73
|
+
runCount: number;
|
|
74
|
+
lastWeeklySummaryAt: string;
|
|
75
|
+
/**
|
|
76
|
+
* When we last suggested /ashlr:ashlr-genome-init for the current cwd.
|
|
77
|
+
* Keyed by cwd so the once-per-week throttle is per-project.
|
|
78
|
+
*/
|
|
79
|
+
lastGenomeSuggestByCwd?: Record<string, string>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type Mode = "first-run" | "normal" | "weekly";
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Paths
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
export function statsPath(home: string = homedir()): string {
|
|
89
|
+
return join(home, ".ashlr", "stats.json");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function sessionStatePath(home: string = homedir()): string {
|
|
93
|
+
return join(home, ".ashlr", "session-state.json");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// TTY + color detection (self-contained — don't pull in ui.ts since hooks run
|
|
98
|
+
// in isolated bun processes and pulling large modules would slow startup).
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
function stderrIsTTY(): boolean {
|
|
102
|
+
return typeof process.stderr?.isTTY === "boolean" ? process.stderr.isTTY : false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function colorEnabled(): boolean {
|
|
106
|
+
if (process.env.NO_COLOR) return false;
|
|
107
|
+
if (process.env.FORCE_COLOR) return true;
|
|
108
|
+
return stderrIsTTY();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function a(code: string, s: string): string {
|
|
112
|
+
return colorEnabled() ? `\x1b[${code}m${s}\x1b[0m` : s;
|
|
113
|
+
}
|
|
114
|
+
const c = {
|
|
115
|
+
dim: (s: string) => a("2", s),
|
|
116
|
+
bold: (s: string) => a("1", s),
|
|
117
|
+
cyan: (s: string) => a("36", s),
|
|
118
|
+
brightCyan: (s: string) => a("96", s),
|
|
119
|
+
green: (s: string) => a("32", s),
|
|
120
|
+
brightGreen: (s: string) => a("92", s),
|
|
121
|
+
yellow: (s: string) => a("33", s),
|
|
122
|
+
brightYellow: (s: string) => a("93", s),
|
|
123
|
+
magenta: (s: string) => a("35", s),
|
|
124
|
+
brightMagenta: (s: string) => a("95", s),
|
|
125
|
+
gray: (s: string) => a("90", s),
|
|
126
|
+
white: (s: string) => a("97", s),
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Formatting
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
import { costFor } from "../servers/_pricing";
|
|
134
|
+
|
|
135
|
+
function fmtUsd(tokens: number): string {
|
|
136
|
+
// v1.22 wiring fix: use the unified _pricing.ts costFor so session-greet
|
|
137
|
+
// matches /ashlr-savings + dashboard + status-line. Previously held a
|
|
138
|
+
// local BLENDED_USD_PER_MTOK = 5 that drifted from the canonical rate.
|
|
139
|
+
const v = costFor(tokens);
|
|
140
|
+
if (v < 0.01) return `$${v.toFixed(4)}`;
|
|
141
|
+
if (v < 1) return `$${v.toFixed(3)}`;
|
|
142
|
+
if (v < 100) return `$${v.toFixed(2)}`;
|
|
143
|
+
return `$${Math.round(v).toLocaleString()}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function fmtTokens(n: number): string {
|
|
147
|
+
if (!Number.isFinite(n) || n < 0) return "0";
|
|
148
|
+
if (n < 1000) return String(Math.floor(n));
|
|
149
|
+
if (n < 1_000_000) return (n / 1000).toFixed(1) + "K";
|
|
150
|
+
if (n < 1_000_000_000) return (n / 1_000_000).toFixed(2) + "M";
|
|
151
|
+
return (n / 1_000_000_000).toFixed(2) + "B";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// File IO — always swallow errors; the greeting is best-effort.
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
export function loadStats(home: string = homedir()): Stats | null {
|
|
159
|
+
try {
|
|
160
|
+
const p = statsPath(home);
|
|
161
|
+
if (!existsSync(p)) return null;
|
|
162
|
+
return JSON.parse(readFileSync(p, "utf-8")) as Stats;
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Load the session-state.json if present and well-formed. On any parse error
|
|
170
|
+
* or missing required shape, return null so the caller treats this run as a
|
|
171
|
+
* first-run and overwrites the corrupt file.
|
|
172
|
+
*/
|
|
173
|
+
export function loadSessionState(home: string = homedir()): SessionState | null {
|
|
174
|
+
try {
|
|
175
|
+
const p = sessionStatePath(home);
|
|
176
|
+
if (!existsSync(p)) return null;
|
|
177
|
+
const raw = JSON.parse(readFileSync(p, "utf-8")) as Partial<SessionState>;
|
|
178
|
+
// Minimum viable shape — anything less and we treat as corrupt.
|
|
179
|
+
if (
|
|
180
|
+
typeof raw.firstRunAt !== "string" ||
|
|
181
|
+
typeof raw.lastRunAt !== "string" ||
|
|
182
|
+
typeof raw.runCount !== "number" ||
|
|
183
|
+
typeof raw.lastWeeklySummaryAt !== "string"
|
|
184
|
+
) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
firstRunAt: raw.firstRunAt,
|
|
189
|
+
lastRunAt: raw.lastRunAt,
|
|
190
|
+
runCount: raw.runCount,
|
|
191
|
+
lastWeeklySummaryAt: raw.lastWeeklySummaryAt,
|
|
192
|
+
lastGenomeSuggestByCwd:
|
|
193
|
+
typeof raw.lastGenomeSuggestByCwd === "object" && raw.lastGenomeSuggestByCwd !== null
|
|
194
|
+
? (raw.lastGenomeSuggestByCwd as Record<string, string>)
|
|
195
|
+
: {},
|
|
196
|
+
};
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function saveSessionState(state: SessionState, home: string = homedir()): void {
|
|
203
|
+
try {
|
|
204
|
+
const p = sessionStatePath(home);
|
|
205
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
206
|
+
writeFileSync(p, JSON.stringify(state, null, 2));
|
|
207
|
+
} catch {
|
|
208
|
+
/* best-effort */
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Mode selection
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Decide which greeting to render.
|
|
220
|
+
*
|
|
221
|
+
* - first-run: no state file, or corrupt state file.
|
|
222
|
+
* - weekly: >=7 days since lastWeeklySummaryAt OR runCount (post-increment)
|
|
223
|
+
* is a multiple of 20. The runCount cadence makes sure heavy
|
|
224
|
+
* users who session-start many times per day still see the
|
|
225
|
+
* weekly digest at a reasonable frequency.
|
|
226
|
+
* - normal: everything else.
|
|
227
|
+
*/
|
|
228
|
+
export function pickMode(state: SessionState | null, now: Date = new Date()): Mode {
|
|
229
|
+
if (!state) return "first-run";
|
|
230
|
+
const lastWeekly = Date.parse(state.lastWeeklySummaryAt);
|
|
231
|
+
if (!Number.isFinite(lastWeekly)) return "weekly";
|
|
232
|
+
if (now.getTime() - lastWeekly >= WEEK_MS) return "weekly";
|
|
233
|
+
// runCount here is the post-increment count for THIS run. A multiple of 20
|
|
234
|
+
// earns a weekly summary even if 7 days haven't elapsed.
|
|
235
|
+
if (state.runCount > 0 && state.runCount % 20 === 0) return "weekly";
|
|
236
|
+
return "normal";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Ollama detection
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Best-effort Ollama check. Uses curl because it's ubiquitous and we don't
|
|
245
|
+
* want to pull a fetch() into a hot hook path. 2-second timeout is enough to
|
|
246
|
+
* either connect to localhost or bail.
|
|
247
|
+
*/
|
|
248
|
+
export function detectOllama(): boolean {
|
|
249
|
+
try {
|
|
250
|
+
const res = spawnSync(
|
|
251
|
+
"curl",
|
|
252
|
+
["-s", "--max-time", "2", "-o", "/dev/null", "-w", "%{http_code}", "http://localhost:11434/api/tags"],
|
|
253
|
+
{ timeout: 3000, stdio: ["ignore", "pipe", "ignore"] },
|
|
254
|
+
);
|
|
255
|
+
if (res.status !== 0) return false;
|
|
256
|
+
const code = (res.stdout?.toString() ?? "").trim();
|
|
257
|
+
return code === "200";
|
|
258
|
+
} catch {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
// Genome detection (for "run genome-init?" nudge)
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
export function hasGenome(cwd: string = process.cwd()): boolean {
|
|
268
|
+
try {
|
|
269
|
+
return existsSync(join(cwd, ".ashlrcode", "genome"));
|
|
270
|
+
} catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// Sparkline
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
const SPARK = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
280
|
+
|
|
281
|
+
function sparkline(values: number[]): string {
|
|
282
|
+
if (values.length === 0) return "";
|
|
283
|
+
const max = Math.max(...values);
|
|
284
|
+
if (max <= 0) return SPARK[0]!.repeat(values.length);
|
|
285
|
+
return values
|
|
286
|
+
.map((v) => {
|
|
287
|
+
if (v <= 0) return c.gray(SPARK[0]!);
|
|
288
|
+
const idx = Math.max(1, Math.min(7, Math.ceil((v / max) * 7)));
|
|
289
|
+
const glyph = SPARK[idx]!;
|
|
290
|
+
if (idx <= 2) return c.cyan(glyph);
|
|
291
|
+
if (idx <= 5) return c.brightCyan(glyph);
|
|
292
|
+
return c.brightGreen(glyph);
|
|
293
|
+
})
|
|
294
|
+
.join("");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function lastNDayKeys(n: number, now: Date = new Date()): string[] {
|
|
298
|
+
const out: string[] = [];
|
|
299
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
300
|
+
const d = new Date(now);
|
|
301
|
+
d.setUTCDate(d.getUTCDate() - i);
|
|
302
|
+
out.push(d.toISOString().slice(0, 10));
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ---------------------------------------------------------------------------
|
|
308
|
+
// Rendering
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
|
|
311
|
+
/** Whether we should render the fancy box-drawing UI vs. plain text. */
|
|
312
|
+
function fancyMode(): boolean {
|
|
313
|
+
if (process.env.NO_COLOR) return false;
|
|
314
|
+
if (process.env.FORCE_COLOR) return true;
|
|
315
|
+
return stderrIsTTY();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* First-run welcome. In fancy mode, renders a bordered box. In non-TTY mode,
|
|
320
|
+
* prints a compact plain-text version.
|
|
321
|
+
*/
|
|
322
|
+
export function renderFirstRun(opts: { ollama: boolean; hasGenome: boolean }): string {
|
|
323
|
+
const lines: string[] = [];
|
|
324
|
+
|
|
325
|
+
if (fancyMode()) {
|
|
326
|
+
// Wide enough to accommodate the longest line without wrapping. 66 cols
|
|
327
|
+
// fits the "Ollama detected — try /ashlr:ashlr-genome-init --summarize"
|
|
328
|
+
// hint plus the leading " ✓ " badge.
|
|
329
|
+
const W = 66;
|
|
330
|
+
const h = (s: string) => c.cyan(s);
|
|
331
|
+
const titleBlock = " " + c.bold(c.brightCyan("Welcome to ashlr")) + " ";
|
|
332
|
+
// "Welcome to ashlr" is 16 chars visible, plus two flanking spaces = 18.
|
|
333
|
+
const titleVisible = 18;
|
|
334
|
+
const dashes = W - 2 - titleVisible;
|
|
335
|
+
const leftDashes = Math.max(2, Math.floor(dashes / 2));
|
|
336
|
+
const rightDashes = Math.max(2, dashes - leftDashes);
|
|
337
|
+
const top =
|
|
338
|
+
h("╔") + h("═".repeat(leftDashes)) + titleBlock + h("═".repeat(rightDashes)) + h("╗");
|
|
339
|
+
const bot = h("╚" + "═".repeat(W - 2) + "╝");
|
|
340
|
+
const pad = (s: string): string => {
|
|
341
|
+
// visibleLen — strip ANSI
|
|
342
|
+
const visible = s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
343
|
+
const rem = W - 4 - visible;
|
|
344
|
+
return h("║") + " " + s + (rem > 0 ? " ".repeat(rem) : "") + " " + h("║");
|
|
345
|
+
};
|
|
346
|
+
const blank = pad("");
|
|
347
|
+
lines.push(top);
|
|
348
|
+
lines.push(blank);
|
|
349
|
+
lines.push(pad(c.dim("Token-efficient Claude Code plugin")));
|
|
350
|
+
lines.push(blank);
|
|
351
|
+
lines.push(pad(c.green(" •") + " " + c.white("ashlr__read") + c.dim(" snipCompact truncation")));
|
|
352
|
+
lines.push(pad(c.green(" •") + " " + c.white("ashlr__grep") + c.dim(" genome RAG retrieval")));
|
|
353
|
+
lines.push(pad(c.green(" •") + " " + c.white("ashlr__edit") + c.dim(" diff-format edits")));
|
|
354
|
+
lines.push(blank);
|
|
355
|
+
lines.push(pad(c.bold("Next steps:")));
|
|
356
|
+
if (!opts.hasGenome) {
|
|
357
|
+
lines.push(pad(c.brightMagenta(" →") + " Run " + c.white("/ashlr:ashlr-genome-init") + c.dim(" in a project")));
|
|
358
|
+
} else {
|
|
359
|
+
lines.push(pad(c.brightMagenta(" →") + " " + c.dim("Genome detected — you're all set.")));
|
|
360
|
+
}
|
|
361
|
+
lines.push(pad(c.brightMagenta(" →") + " Run " + c.white("/ashlr:ashlr-dashboard") + c.dim(" to see savings")));
|
|
362
|
+
lines.push(pad(c.brightMagenta(" →") + " Run " + c.white("/ashlr:ashlr-tour") + c.dim(" for a 60s walkthrough")));
|
|
363
|
+
lines.push(blank);
|
|
364
|
+
if (opts.ollama) {
|
|
365
|
+
lines.push(
|
|
366
|
+
pad(
|
|
367
|
+
c.brightGreen(" ✓") +
|
|
368
|
+
" " +
|
|
369
|
+
c.dim("Ollama detected — try ") +
|
|
370
|
+
c.white("/ashlr:ashlr-genome-init --summarize"),
|
|
371
|
+
),
|
|
372
|
+
);
|
|
373
|
+
} else {
|
|
374
|
+
lines.push(
|
|
375
|
+
pad(
|
|
376
|
+
c.cyan(" ℹ") +
|
|
377
|
+
" " +
|
|
378
|
+
c.dim("Optional: install Ollama + ") +
|
|
379
|
+
c.white("llama3.2:3b") +
|
|
380
|
+
c.dim(" for local"),
|
|
381
|
+
),
|
|
382
|
+
);
|
|
383
|
+
lines.push(pad(c.dim(" LLM summaries — see ") + c.white("/ashlr:ashlr-ollama-setup")));
|
|
384
|
+
}
|
|
385
|
+
lines.push(blank);
|
|
386
|
+
lines.push(bot);
|
|
387
|
+
} else {
|
|
388
|
+
// Plain-text fallback — no boxes, no colors (already disabled by TTY check).
|
|
389
|
+
lines.push("Welcome to ashlr — token-efficient Claude Code plugin");
|
|
390
|
+
lines.push(" • ashlr__read — snipCompact truncation");
|
|
391
|
+
lines.push(" • ashlr__grep — genome RAG retrieval");
|
|
392
|
+
lines.push(" • ashlr__edit — diff-format edits");
|
|
393
|
+
lines.push("Next steps:");
|
|
394
|
+
if (!opts.hasGenome) {
|
|
395
|
+
lines.push(" → Run /ashlr:ashlr-genome-init in a project");
|
|
396
|
+
}
|
|
397
|
+
lines.push(" → Run /ashlr:ashlr-dashboard to see savings");
|
|
398
|
+
lines.push(" → Run /ashlr:ashlr-tour for a 60s walkthrough");
|
|
399
|
+
if (opts.ollama) {
|
|
400
|
+
lines.push(" ✓ Ollama detected — try /ashlr:ashlr-genome-init --summarize");
|
|
401
|
+
} else {
|
|
402
|
+
lines.push(" ℹ Optional: install Ollama + llama3.2:3b — see /ashlr:ashlr-ollama-setup");
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return lines.join("\n");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Normal-run 1-liner. Shows the delta from last session + lifetime totals.
|
|
411
|
+
*
|
|
412
|
+
* "Last session" means the totals currently in stats.json under `session`
|
|
413
|
+
* — the efficiency server resets that block per-session, so at SessionStart
|
|
414
|
+
* time it still reflects the PREVIOUS session's work.
|
|
415
|
+
*/
|
|
416
|
+
export function renderNormal(stats: Stats | null): string {
|
|
417
|
+
const lifeTok = stats?.lifetime?.tokensSaved ?? 0;
|
|
418
|
+
const sessTok = stats?.session?.tokensSaved ?? 0;
|
|
419
|
+
|
|
420
|
+
if (lifeTok === 0 && sessTok === 0) {
|
|
421
|
+
// No usage yet — give them a gentle poke rather than a blank stat.
|
|
422
|
+
return c.brightMagenta("ashlr") + c.dim(" ⏵ ") + c.dim("no savings yet — try ") + c.white("ashlr__read") + c.dim(" on any file.");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const prefix = c.brightMagenta("ashlr") + c.dim(" ⏵ ");
|
|
426
|
+
const sessionPart =
|
|
427
|
+
sessTok > 0
|
|
428
|
+
? c.dim("last session saved ") +
|
|
429
|
+
c.brightGreen(fmtTokens(sessTok) + " tok") +
|
|
430
|
+
c.dim(" (") +
|
|
431
|
+
c.brightYellow(fmtUsd(sessTok)) +
|
|
432
|
+
c.dim(").")
|
|
433
|
+
: c.dim("no savings last session.");
|
|
434
|
+
const lifetimePart =
|
|
435
|
+
c.dim(" Lifetime: ") +
|
|
436
|
+
c.brightGreen(fmtTokens(lifeTok)) +
|
|
437
|
+
c.dim(" (") +
|
|
438
|
+
c.brightYellow(fmtUsd(lifeTok)) +
|
|
439
|
+
c.dim(").");
|
|
440
|
+
return prefix + sessionPart + lifetimePart;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Weekly compact dashboard — 7-day sparkline + totals + top tool, and a
|
|
445
|
+
* pointer to the full /ashlr:ashlr-dashboard.
|
|
446
|
+
*/
|
|
447
|
+
export function renderWeekly(stats: Stats | null, now: Date = new Date()): string {
|
|
448
|
+
const byDay = stats?.lifetime?.byDay ?? {};
|
|
449
|
+
const keys = lastNDayKeys(7, now);
|
|
450
|
+
const values = keys.map((k) => byDay[k]?.tokensSaved ?? 0);
|
|
451
|
+
const weekTotal = values.reduce((s, v) => s + v, 0);
|
|
452
|
+
const activeDays = values.filter((v) => v > 0).length;
|
|
453
|
+
|
|
454
|
+
// Top tool by lifetime tokens saved — quickest way to surface "what's
|
|
455
|
+
// actually working for you".
|
|
456
|
+
const byTool = stats?.lifetime?.byTool ?? {};
|
|
457
|
+
let topName = "";
|
|
458
|
+
let topTok = 0;
|
|
459
|
+
for (const [name, t] of Object.entries(byTool)) {
|
|
460
|
+
const tok = t?.tokensSaved ?? 0;
|
|
461
|
+
if (tok > topTok) {
|
|
462
|
+
topTok = tok;
|
|
463
|
+
topName = name;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const lines: string[] = [];
|
|
468
|
+
if (fancyMode()) {
|
|
469
|
+
lines.push(c.brightMagenta("ashlr") + c.dim(" ⏵ ") + c.bold(c.brightCyan("Weekly summary")));
|
|
470
|
+
lines.push(" " + sparkline(values) + c.dim(" last 7 days"));
|
|
471
|
+
lines.push(
|
|
472
|
+
" " +
|
|
473
|
+
c.dim("total ") +
|
|
474
|
+
c.brightGreen(fmtTokens(weekTotal).padStart(8)) +
|
|
475
|
+
c.dim(" ") +
|
|
476
|
+
c.brightYellow(fmtUsd(weekTotal)),
|
|
477
|
+
);
|
|
478
|
+
lines.push(
|
|
479
|
+
" " +
|
|
480
|
+
c.dim("active ") +
|
|
481
|
+
c.white(String(activeDays).padStart(8)) +
|
|
482
|
+
c.dim(" days of 7"),
|
|
483
|
+
);
|
|
484
|
+
if (topName) {
|
|
485
|
+
lines.push(
|
|
486
|
+
" " +
|
|
487
|
+
c.dim("top ") +
|
|
488
|
+
c.white(topName.padStart(16)) +
|
|
489
|
+
c.dim(" ") +
|
|
490
|
+
c.brightGreen(fmtTokens(topTok)),
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
lines.push(" " + c.dim("full view: ") + c.white("/ashlr:ashlr-dashboard"));
|
|
494
|
+
} else {
|
|
495
|
+
lines.push(`ashlr ⏵ weekly: ${fmtTokens(weekTotal)} tok (${fmtUsd(weekTotal)}) over ${activeDays}/7 days` +
|
|
496
|
+
(topName ? ` · top: ${topName} (${fmtTokens(topTok)})` : "") +
|
|
497
|
+
" · /ashlr:ashlr-dashboard for full view");
|
|
498
|
+
}
|
|
499
|
+
return lines.join("\n");
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// ---------------------------------------------------------------------------
|
|
503
|
+
// Genome-init suggestion (once per week per cwd)
|
|
504
|
+
// ---------------------------------------------------------------------------
|
|
505
|
+
|
|
506
|
+
export function maybeGenomeSuggestion(
|
|
507
|
+
state: SessionState,
|
|
508
|
+
cwd: string,
|
|
509
|
+
now: Date = new Date(),
|
|
510
|
+
): { suggest: boolean; stateMutated: SessionState } {
|
|
511
|
+
// Only relevant after the user has taken a few sessions to settle in.
|
|
512
|
+
if (state.runCount <= 2) return { suggest: false, stateMutated: state };
|
|
513
|
+
if (hasGenome(cwd)) return { suggest: false, stateMutated: state };
|
|
514
|
+
|
|
515
|
+
const table = { ...(state.lastGenomeSuggestByCwd ?? {}) };
|
|
516
|
+
const last = table[cwd];
|
|
517
|
+
if (last) {
|
|
518
|
+
const ts = Date.parse(last);
|
|
519
|
+
if (Number.isFinite(ts) && now.getTime() - ts < WEEK_MS) {
|
|
520
|
+
return { suggest: false, stateMutated: state };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
table[cwd] = now.toISOString();
|
|
524
|
+
return {
|
|
525
|
+
suggest: true,
|
|
526
|
+
stateMutated: { ...state, lastGenomeSuggestByCwd: table },
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function renderGenomeSuggestion(cwd: string): string {
|
|
531
|
+
const short = cwd.replace(homedir(), "~");
|
|
532
|
+
return (
|
|
533
|
+
c.cyan("ashlr ⏵ ") +
|
|
534
|
+
c.dim("no genome in ") +
|
|
535
|
+
c.white(short) +
|
|
536
|
+
c.dim(" — run ") +
|
|
537
|
+
c.white("/ashlr:ashlr-genome-init") +
|
|
538
|
+
c.dim(" for ~84% grep savings.")
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// ---------------------------------------------------------------------------
|
|
543
|
+
// Orchestration
|
|
544
|
+
// ---------------------------------------------------------------------------
|
|
545
|
+
|
|
546
|
+
export interface GreetOpts {
|
|
547
|
+
home?: string;
|
|
548
|
+
cwd?: string;
|
|
549
|
+
now?: Date;
|
|
550
|
+
/** For tests — swap in a canned stats object instead of reading disk. */
|
|
551
|
+
stats?: Stats | null;
|
|
552
|
+
/** For tests — skip the real ollama probe. */
|
|
553
|
+
ollamaDetector?: () => boolean;
|
|
554
|
+
/** For tests — capture output instead of writing to stderr. */
|
|
555
|
+
write?: (s: string) => void;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export interface GreetResult {
|
|
559
|
+
mode: Mode;
|
|
560
|
+
printed: boolean;
|
|
561
|
+
genomeSuggested: boolean;
|
|
562
|
+
state: SessionState;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* The business end: pick a mode, write the appropriate greeting, update the
|
|
567
|
+
* session-state file. Returns a structured result for testing + logging.
|
|
568
|
+
*/
|
|
569
|
+
export function greet(opts: GreetOpts = {}): GreetResult {
|
|
570
|
+
const home = opts.home ?? homedir();
|
|
571
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
572
|
+
const now = opts.now ?? new Date();
|
|
573
|
+
const write = opts.write ?? ((s: string) => process.stderr.write(s));
|
|
574
|
+
|
|
575
|
+
const quiet = process.env.ASHLR_QUIET === "1";
|
|
576
|
+
const noGreet = process.env.ASHLR_NO_GREET === "1";
|
|
577
|
+
|
|
578
|
+
const stats = opts.stats !== undefined ? opts.stats : loadStats(home);
|
|
579
|
+
const prior = loadSessionState(home);
|
|
580
|
+
|
|
581
|
+
let mode: Mode;
|
|
582
|
+
let nextState: SessionState;
|
|
583
|
+
|
|
584
|
+
if (!prior) {
|
|
585
|
+
mode = "first-run";
|
|
586
|
+
nextState = {
|
|
587
|
+
firstRunAt: now.toISOString(),
|
|
588
|
+
lastRunAt: now.toISOString(),
|
|
589
|
+
runCount: 1,
|
|
590
|
+
lastWeeklySummaryAt: now.toISOString(),
|
|
591
|
+
lastGenomeSuggestByCwd: {},
|
|
592
|
+
};
|
|
593
|
+
} else {
|
|
594
|
+
const nextCount = prior.runCount + 1;
|
|
595
|
+
// Preliminary state (runCount bumped) so pickMode sees the right count.
|
|
596
|
+
const interim: SessionState = {
|
|
597
|
+
...prior,
|
|
598
|
+
lastRunAt: now.toISOString(),
|
|
599
|
+
runCount: nextCount,
|
|
600
|
+
};
|
|
601
|
+
mode = pickMode(interim, now);
|
|
602
|
+
nextState = interim;
|
|
603
|
+
if (mode === "weekly") {
|
|
604
|
+
nextState = { ...nextState, lastWeeklySummaryAt: now.toISOString() };
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
let printed = false;
|
|
609
|
+
let genomeSuggested = false;
|
|
610
|
+
|
|
611
|
+
if (!quiet && !noGreet) {
|
|
612
|
+
if (mode === "first-run") {
|
|
613
|
+
const ollama = (opts.ollamaDetector ?? detectOllama)();
|
|
614
|
+
write(renderFirstRun({ ollama, hasGenome: hasGenome(cwd) }) + "\n");
|
|
615
|
+
printed = true;
|
|
616
|
+
} else if (mode === "weekly") {
|
|
617
|
+
write(renderWeekly(stats, now) + "\n");
|
|
618
|
+
printed = true;
|
|
619
|
+
} else {
|
|
620
|
+
write(renderNormal(stats) + "\n");
|
|
621
|
+
printed = true;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Optional per-cwd genome nudge (excluded on first-run to avoid overload).
|
|
625
|
+
if (mode !== "first-run") {
|
|
626
|
+
const g = maybeGenomeSuggestion(nextState, cwd, now);
|
|
627
|
+
if (g.suggest) {
|
|
628
|
+
write(renderGenomeSuggestion(cwd) + "\n");
|
|
629
|
+
genomeSuggested = true;
|
|
630
|
+
}
|
|
631
|
+
nextState = g.stateMutated;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Weekly digest banner — fires once per ISO week when savings > $0.
|
|
635
|
+
// Rendered after the normal greet so it's the last thing in the transcript.
|
|
636
|
+
if (mode !== "first-run") {
|
|
637
|
+
try {
|
|
638
|
+
const prior = priorWeekKey(now);
|
|
639
|
+
const dayKeys = weekDayKeys(prior);
|
|
640
|
+
const byDay = stats?.lifetime?.byDay ?? {};
|
|
641
|
+
const weekTokens = dayKeys.reduce(
|
|
642
|
+
(sum: number, k: string) => sum + ((byDay[k]?.tokensSaved) ?? 0),
|
|
643
|
+
0,
|
|
644
|
+
);
|
|
645
|
+
const dollarsSaved = costFor(weekTokens);
|
|
646
|
+
const streakData = readStreaks(home);
|
|
647
|
+
const digest = buildDigestBanner({
|
|
648
|
+
home,
|
|
649
|
+
now,
|
|
650
|
+
// Cast through unknown: local ByDay/ByTool index values are `T | undefined`
|
|
651
|
+
// while DigestStats expects `T` — structurally compatible at runtime.
|
|
652
|
+
stats: { lifetime: {
|
|
653
|
+
byDay: stats?.lifetime?.byDay as Record<string, { tokensSaved?: number }> | undefined,
|
|
654
|
+
byTool: stats?.lifetime?.byTool as Record<string, { tokensSaved?: number }> | undefined,
|
|
655
|
+
} },
|
|
656
|
+
dollarsSaved,
|
|
657
|
+
currentStreak: streakData.currentStreak,
|
|
658
|
+
});
|
|
659
|
+
if (digest.banner) {
|
|
660
|
+
write("\n" + digest.banner + "\n");
|
|
661
|
+
}
|
|
662
|
+
} catch {
|
|
663
|
+
/* digest is decoration — never break the greeting */
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Always persist state — even under ASHLR_QUIET — so the first-run detector
|
|
669
|
+
// remains accurate.
|
|
670
|
+
saveSessionState(nextState, home);
|
|
671
|
+
|
|
672
|
+
return { mode, printed, genomeSuggested, state: nextState };
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ---------------------------------------------------------------------------
|
|
676
|
+
// CLI entry
|
|
677
|
+
// ---------------------------------------------------------------------------
|
|
678
|
+
|
|
679
|
+
if (import.meta.main) {
|
|
680
|
+
try {
|
|
681
|
+
greet();
|
|
682
|
+
} catch {
|
|
683
|
+
// Swallow — greeting must never break session startup.
|
|
684
|
+
}
|
|
685
|
+
process.exit(0);
|
|
686
|
+
}
|