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,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nudge telemetry — records shown/clicked/dismissed events for the upgrade nudge.
|
|
3
|
+
*
|
|
4
|
+
* Writes JSONL to ~/.ashlr/nudge-events.jsonl. Kept separate from
|
|
5
|
+
* session-log.jsonl because its retention needs are different (we care about
|
|
6
|
+
* long-running conversion rates, not per-call observability) and because it
|
|
7
|
+
* is safer to ship the backend sync on a file that isn't used by anything else.
|
|
8
|
+
*
|
|
9
|
+
* Privacy invariants:
|
|
10
|
+
* - sessionId is hashed before write (not the raw CLAUDE_SESSION_ID).
|
|
11
|
+
* - tokenCount is bucketed (50k / 100k / 500k / 1m) — never exact.
|
|
12
|
+
* - No cwd, no paths, no filenames, no user-supplied strings.
|
|
13
|
+
*
|
|
14
|
+
* Contract: every public function is best-effort; none throw.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createHash, randomUUID } from "crypto";
|
|
18
|
+
import { appendFile, mkdir, readFile, rename, stat, writeFile } from "fs/promises";
|
|
19
|
+
import { existsSync, readFileSync } from "fs";
|
|
20
|
+
import { homedir } from "os";
|
|
21
|
+
import { dirname, join } from "path";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Types
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
export type NudgeEventKind =
|
|
28
|
+
| "nudge_shown"
|
|
29
|
+
| "nudge_clicked"
|
|
30
|
+
| "nudge_dismissed_implicitly";
|
|
31
|
+
|
|
32
|
+
export interface NudgeEvent {
|
|
33
|
+
ts: string;
|
|
34
|
+
event: NudgeEventKind;
|
|
35
|
+
sessionId: string;
|
|
36
|
+
tokenCount: number; // bucketed
|
|
37
|
+
variant: string;
|
|
38
|
+
nudgeId: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Paths
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
function home(override?: string): string {
|
|
46
|
+
return override ?? process.env.HOME ?? homedir();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function nudgeEventsPath(homeOverride?: string): string {
|
|
50
|
+
return join(home(homeOverride), ".ashlr", "nudge-events.jsonl");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function sessionStatePath(homeOverride?: string): string {
|
|
54
|
+
return join(home(homeOverride), ".ashlr", "nudge-session-state.json");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function proTokenPath(homeOverride?: string): string {
|
|
58
|
+
return join(home(homeOverride), ".ashlr", "pro-token");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Hashing / bucketing
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
/** Hash a raw session id to a short, non-reversible digest. */
|
|
66
|
+
export function hashSessionId(raw: string): string {
|
|
67
|
+
return createHash("sha256").update(`ashlr-nudge:${raw}`).digest("hex").slice(0, 16);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Map an exact token count into one of the published buckets.
|
|
72
|
+
* Buckets: 0 (below-threshold), 50k, 100k, 500k, 1m.
|
|
73
|
+
* Anything below 50k is 0 — we never want exact sub-threshold counts flowing
|
|
74
|
+
* through the telemetry payload.
|
|
75
|
+
*/
|
|
76
|
+
export function bucketTokenCount(n: number): number {
|
|
77
|
+
if (!Number.isFinite(n) || n < 50_000) return 0;
|
|
78
|
+
if (n >= 1_000_000) return 1_000_000;
|
|
79
|
+
if (n >= 500_000) return 500_000;
|
|
80
|
+
if (n >= 100_000) return 100_000;
|
|
81
|
+
return 50_000;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Session-scoped dedupe state
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
//
|
|
88
|
+
// We store one small json file per session so the status line can dedupe
|
|
89
|
+
// nudge_shown events across its many invocations without burning a hit per
|
|
90
|
+
// render. The file holds the first-shown timestamp + the nudgeId so a later
|
|
91
|
+
// /ashlr-upgrade invocation (in the same session) can correlate to it.
|
|
92
|
+
|
|
93
|
+
interface NudgeSessionState {
|
|
94
|
+
sessionHash: string;
|
|
95
|
+
nudgeId: string;
|
|
96
|
+
firstShownAt: string; // ISO
|
|
97
|
+
lastShownAt: string;
|
|
98
|
+
clicked: boolean;
|
|
99
|
+
variant: string;
|
|
100
|
+
tokenBucket: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const SHOWN_DEDUPE_MS = 60 * 60 * 1000; // once per hour
|
|
104
|
+
|
|
105
|
+
async function readSessionState(homeOverride?: string): Promise<NudgeSessionState | null> {
|
|
106
|
+
try {
|
|
107
|
+
const raw = await readFile(sessionStatePath(homeOverride), "utf-8");
|
|
108
|
+
return JSON.parse(raw) as NudgeSessionState;
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function writeSessionState(s: NudgeSessionState, homeOverride?: string): Promise<void> {
|
|
115
|
+
try {
|
|
116
|
+
await mkdir(dirname(sessionStatePath(homeOverride)), { recursive: true });
|
|
117
|
+
await writeFile(sessionStatePath(homeOverride), JSON.stringify(s));
|
|
118
|
+
} catch { /* best-effort */ }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Append helpers
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
const MAX_JSONL_BYTES = 10 * 1024 * 1024;
|
|
126
|
+
|
|
127
|
+
async function rotateIfLarge(p: string): Promise<void> {
|
|
128
|
+
try {
|
|
129
|
+
const st = await stat(p);
|
|
130
|
+
if (st.size < MAX_JSONL_BYTES) return;
|
|
131
|
+
const r1 = p + ".1";
|
|
132
|
+
const r2 = p + ".2";
|
|
133
|
+
// best-effort: cascade so a prior .1 isn't clobbered on repeat rotation
|
|
134
|
+
try { await rename(r1, r2); } catch { /* no prior .1 */ }
|
|
135
|
+
await rename(p, r1);
|
|
136
|
+
} catch { /* file doesn't exist or stat/rename failed — append will create */ }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function appendJsonl(rec: NudgeEvent, homeOverride?: string): Promise<void> {
|
|
140
|
+
try {
|
|
141
|
+
const p = nudgeEventsPath(homeOverride);
|
|
142
|
+
await mkdir(dirname(p), { recursive: true });
|
|
143
|
+
await rotateIfLarge(p);
|
|
144
|
+
await appendFile(p, JSON.stringify(rec) + "\n", "utf-8");
|
|
145
|
+
} catch { /* best-effort */ }
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function rawSessionId(): string {
|
|
149
|
+
const explicit = process.env.CLAUDE_SESSION_ID ?? process.env.ASHLR_SESSION_ID;
|
|
150
|
+
if (explicit && explicit.trim().length > 0) return explicit.trim();
|
|
151
|
+
const seed = `ppid:${process.ppid ?? "?"}:${process.env.HOME ?? ""}`;
|
|
152
|
+
let h = 0;
|
|
153
|
+
for (let i = 0; i < seed.length; i++) h = ((h << 5) - h + seed.charCodeAt(i)) | 0;
|
|
154
|
+
return `p${(h >>> 0).toString(16)}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Public API
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
export interface RecordShownOpts {
|
|
162
|
+
/** Raw session id, prior to hashing. Defaults to CLAUDE_SESSION_ID / PPID fallback. */
|
|
163
|
+
rawSessionId?: string;
|
|
164
|
+
/** Exact token count — will be bucketed. */
|
|
165
|
+
tokenCount: number;
|
|
166
|
+
/** Copy variant, e.g. "v1". Default "v1". */
|
|
167
|
+
variant?: string;
|
|
168
|
+
/** Inject now() for tests. */
|
|
169
|
+
now?: number;
|
|
170
|
+
/** HOME override for tests / sandboxed renders. */
|
|
171
|
+
home?: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface RecordShownResult {
|
|
175
|
+
/** True iff a new event was written (vs. suppressed by dedupe). */
|
|
176
|
+
wrote: boolean;
|
|
177
|
+
/** The nudgeId the caller should use for correlation. */
|
|
178
|
+
nudgeId: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Log a nudge_shown event for the current session. Dedupes so we record
|
|
183
|
+
* at most one per session per hour — the status line fires every poll tick
|
|
184
|
+
* and we don't want a 50 Hz heartbeat of "nudge_shown".
|
|
185
|
+
*/
|
|
186
|
+
export async function recordNudgeShown(opts: RecordShownOpts): Promise<RecordShownResult> {
|
|
187
|
+
const now = opts.now ?? Date.now();
|
|
188
|
+
const sessionHash = hashSessionId(opts.rawSessionId ?? rawSessionId());
|
|
189
|
+
const variant = opts.variant ?? "v1";
|
|
190
|
+
const tokenBucket = bucketTokenCount(opts.tokenCount);
|
|
191
|
+
const existing = await readSessionState(opts.home);
|
|
192
|
+
|
|
193
|
+
// Same session, within dedupe window → suppress the log line but return the
|
|
194
|
+
// existing nudgeId so clicks still correlate.
|
|
195
|
+
if (existing && existing.sessionHash === sessionHash) {
|
|
196
|
+
const lastMs = Date.parse(existing.lastShownAt);
|
|
197
|
+
if (Number.isFinite(lastMs) && now - lastMs < SHOWN_DEDUPE_MS) {
|
|
198
|
+
return { wrote: false, nudgeId: existing.nudgeId };
|
|
199
|
+
}
|
|
200
|
+
// Past the window — refresh the timestamp, reuse the nudgeId.
|
|
201
|
+
const next: NudgeSessionState = {
|
|
202
|
+
...existing,
|
|
203
|
+
lastShownAt: new Date(now).toISOString(),
|
|
204
|
+
tokenBucket,
|
|
205
|
+
variant,
|
|
206
|
+
};
|
|
207
|
+
await writeSessionState(next, opts.home);
|
|
208
|
+
const rec: NudgeEvent = {
|
|
209
|
+
ts: next.lastShownAt,
|
|
210
|
+
event: "nudge_shown",
|
|
211
|
+
sessionId: sessionHash,
|
|
212
|
+
tokenCount: tokenBucket,
|
|
213
|
+
variant,
|
|
214
|
+
nudgeId: existing.nudgeId,
|
|
215
|
+
};
|
|
216
|
+
await appendJsonl(rec, opts.home);
|
|
217
|
+
return { wrote: true, nudgeId: existing.nudgeId };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// New session → mint a nudgeId and persist state.
|
|
221
|
+
const nudgeId = randomUUID();
|
|
222
|
+
const nowIso = new Date(now).toISOString();
|
|
223
|
+
const state: NudgeSessionState = {
|
|
224
|
+
sessionHash,
|
|
225
|
+
nudgeId,
|
|
226
|
+
firstShownAt: nowIso,
|
|
227
|
+
lastShownAt: nowIso,
|
|
228
|
+
clicked: false,
|
|
229
|
+
variant,
|
|
230
|
+
tokenBucket,
|
|
231
|
+
};
|
|
232
|
+
await writeSessionState(state, opts.home);
|
|
233
|
+
|
|
234
|
+
const rec: NudgeEvent = {
|
|
235
|
+
ts: nowIso,
|
|
236
|
+
event: "nudge_shown",
|
|
237
|
+
sessionId: sessionHash,
|
|
238
|
+
tokenCount: tokenBucket,
|
|
239
|
+
variant,
|
|
240
|
+
nudgeId,
|
|
241
|
+
};
|
|
242
|
+
await appendJsonl(rec, opts.home);
|
|
243
|
+
return { wrote: true, nudgeId };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface RecordClickedOpts {
|
|
247
|
+
rawSessionId?: string;
|
|
248
|
+
/** Maximum correlation window (ms). Default 30 minutes. */
|
|
249
|
+
windowMs?: number;
|
|
250
|
+
variant?: string;
|
|
251
|
+
now?: number;
|
|
252
|
+
home?: string;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Log a nudge_clicked event iff we can correlate it to a recent nudge_shown
|
|
257
|
+
* in the same session within the window. If no shown event is present or
|
|
258
|
+
* the window has elapsed, we still log an "orphan" click (nudgeId "none")
|
|
259
|
+
* so the backend can spot unattributed clicks.
|
|
260
|
+
*/
|
|
261
|
+
export async function recordNudgeClicked(opts: RecordClickedOpts = {}): Promise<void> {
|
|
262
|
+
const now = opts.now ?? Date.now();
|
|
263
|
+
const windowMs = opts.windowMs ?? 30 * 60 * 1000;
|
|
264
|
+
const sessionHash = hashSessionId(opts.rawSessionId ?? rawSessionId());
|
|
265
|
+
const state = await readSessionState(opts.home);
|
|
266
|
+
|
|
267
|
+
let nudgeId = "none";
|
|
268
|
+
let tokenBucket = 0;
|
|
269
|
+
let variant = opts.variant ?? "v1";
|
|
270
|
+
if (state && state.sessionHash === sessionHash) {
|
|
271
|
+
const shownMs = Date.parse(state.firstShownAt);
|
|
272
|
+
if (Number.isFinite(shownMs) && now - shownMs <= windowMs) {
|
|
273
|
+
nudgeId = state.nudgeId;
|
|
274
|
+
tokenBucket = state.tokenBucket;
|
|
275
|
+
variant = state.variant;
|
|
276
|
+
// Mark clicked so the SessionEnd hook doesn't re-emit a dismissal.
|
|
277
|
+
await writeSessionState({ ...state, clicked: true }, opts.home);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const rec: NudgeEvent = {
|
|
282
|
+
ts: new Date(now).toISOString(),
|
|
283
|
+
event: "nudge_clicked",
|
|
284
|
+
sessionId: sessionHash,
|
|
285
|
+
tokenCount: tokenBucket,
|
|
286
|
+
variant,
|
|
287
|
+
nudgeId,
|
|
288
|
+
};
|
|
289
|
+
await appendJsonl(rec, opts.home);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Log a nudge_dismissed_implicitly event when the session ends and a
|
|
294
|
+
* nudge_shown exists but no click was recorded.
|
|
295
|
+
*/
|
|
296
|
+
export async function recordNudgeDismissedIfPending(opts: { rawSessionId?: string; now?: number; home?: string } = {}): Promise<boolean> {
|
|
297
|
+
const now = opts.now ?? Date.now();
|
|
298
|
+
const sessionHash = hashSessionId(opts.rawSessionId ?? rawSessionId());
|
|
299
|
+
const state = await readSessionState(opts.home);
|
|
300
|
+
if (!state || state.sessionHash !== sessionHash || state.clicked) return false;
|
|
301
|
+
|
|
302
|
+
const rec: NudgeEvent = {
|
|
303
|
+
ts: new Date(now).toISOString(),
|
|
304
|
+
event: "nudge_dismissed_implicitly",
|
|
305
|
+
sessionId: sessionHash,
|
|
306
|
+
tokenCount: state.tokenBucket,
|
|
307
|
+
variant: state.variant,
|
|
308
|
+
nudgeId: state.nudgeId,
|
|
309
|
+
};
|
|
310
|
+
await appendJsonl(rec, opts.home);
|
|
311
|
+
// Clear the state file so a later session reusing the same home dir
|
|
312
|
+
// doesn't double-count.
|
|
313
|
+
try { await writeFile(sessionStatePath(opts.home), "{}"); } catch { /* best-effort */ }
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// Aggregation — used by /ashlr-savings to surface the nudge section
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
export interface NudgeSummary {
|
|
322
|
+
shown: number;
|
|
323
|
+
clicked: number;
|
|
324
|
+
dismissed: number;
|
|
325
|
+
conversionPct: number; // 0..100, rounded to 1 decimal; 0 when shown === 0
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function emptySummary(): NudgeSummary {
|
|
329
|
+
return { shown: 0, clicked: 0, dismissed: 0, conversionPct: 0 };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function summariseJsonl(raw: string): NudgeSummary {
|
|
333
|
+
const s = emptySummary();
|
|
334
|
+
for (const line of raw.split("\n")) {
|
|
335
|
+
const trimmed = line.trim();
|
|
336
|
+
if (!trimmed) continue;
|
|
337
|
+
try {
|
|
338
|
+
const r = JSON.parse(trimmed) as Partial<NudgeEvent>;
|
|
339
|
+
if (r.event === "nudge_shown") s.shown += 1;
|
|
340
|
+
else if (r.event === "nudge_clicked") s.clicked += 1;
|
|
341
|
+
else if (r.event === "nudge_dismissed_implicitly") s.dismissed += 1;
|
|
342
|
+
} catch { /* skip malformed */ }
|
|
343
|
+
}
|
|
344
|
+
s.conversionPct = s.shown > 0 ? Math.round((s.clicked / s.shown) * 1000) / 10 : 0;
|
|
345
|
+
return s;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Read the jsonl log and summarise it. Missing / malformed lines are skipped.
|
|
350
|
+
* Never throws.
|
|
351
|
+
*/
|
|
352
|
+
export async function readNudgeSummary(homeOverride?: string): Promise<NudgeSummary> {
|
|
353
|
+
const p = nudgeEventsPath(homeOverride);
|
|
354
|
+
if (!existsSync(p)) return emptySummary();
|
|
355
|
+
let raw = "";
|
|
356
|
+
try { raw = await readFile(p, "utf-8"); } catch { return emptySummary(); }
|
|
357
|
+
return summariseJsonl(raw);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Synchronous counterpart — used by the dashboard renderer, which is sync and
|
|
362
|
+
* runs under setInterval in watch mode. Same guarantees as readNudgeSummary.
|
|
363
|
+
*/
|
|
364
|
+
export function readNudgeSummarySync(homeOverride?: string): NudgeSummary {
|
|
365
|
+
const p = nudgeEventsPath(homeOverride);
|
|
366
|
+
if (!existsSync(p)) return emptySummary();
|
|
367
|
+
let raw = "";
|
|
368
|
+
try { raw = readFileSync(p, "utf-8"); } catch { return emptySummary(); }
|
|
369
|
+
return summariseJsonl(raw);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
// Cloud sync (best-effort, opt-in via pro-token presence)
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
|
|
376
|
+
/** Epoch ms of the last sync attempt — throttles syncs to once per interval. */
|
|
377
|
+
let _lastSync = 0;
|
|
378
|
+
const SYNC_INTERVAL_MS = 5 * 60 * 1000;
|
|
379
|
+
|
|
380
|
+
/** Test hook — reset throttle. */
|
|
381
|
+
export function _resetSyncThrottle(): void { _lastSync = 0; }
|
|
382
|
+
|
|
383
|
+
async function readProToken(): Promise<string | null> {
|
|
384
|
+
const env = process.env["ASHLR_PRO_TOKEN"];
|
|
385
|
+
if (env && env.trim()) return env.trim();
|
|
386
|
+
try {
|
|
387
|
+
const raw = await readFile(proTokenPath(), "utf-8");
|
|
388
|
+
const tok = raw.trim();
|
|
389
|
+
return tok.length > 0 ? tok : null;
|
|
390
|
+
} catch { return null; }
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Fire-and-forget upload of recent nudge events to the ashlr backend. Gated
|
|
395
|
+
* on a valid pro-token; throttled to once per 5 minutes. Drops on any error.
|
|
396
|
+
*/
|
|
397
|
+
export function maybeSyncToCloud(opts: { now?: number } = {}): void {
|
|
398
|
+
const now = opts.now ?? Date.now();
|
|
399
|
+
if (process.env["ASHLR_STATS_UPLOAD"] === "0") return;
|
|
400
|
+
if (now - _lastSync < SYNC_INTERVAL_MS) return;
|
|
401
|
+
_lastSync = now;
|
|
402
|
+
void _doSync();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function _doSync(): Promise<void> {
|
|
406
|
+
try {
|
|
407
|
+
const token = await readProToken();
|
|
408
|
+
if (!token) return;
|
|
409
|
+
const p = nudgeEventsPath();
|
|
410
|
+
if (!existsSync(p)) return;
|
|
411
|
+
let raw = "";
|
|
412
|
+
try { raw = await readFile(p, "utf-8"); } catch { return; }
|
|
413
|
+
const events: NudgeEvent[] = [];
|
|
414
|
+
for (const line of raw.split("\n")) {
|
|
415
|
+
const t = line.trim();
|
|
416
|
+
if (!t) continue;
|
|
417
|
+
try { events.push(JSON.parse(t) as NudgeEvent); } catch { /* skip */ }
|
|
418
|
+
}
|
|
419
|
+
if (events.length === 0) return;
|
|
420
|
+
|
|
421
|
+
const apiUrl = process.env["ASHLR_API_URL"] ?? "https://api.ashlr.ai";
|
|
422
|
+
await fetch(`${apiUrl}/events/nudge`, {
|
|
423
|
+
method: "POST",
|
|
424
|
+
headers: {
|
|
425
|
+
"Content-Type": "application/json",
|
|
426
|
+
Authorization: `Bearer ${token}`,
|
|
427
|
+
},
|
|
428
|
+
body: JSON.stringify({ events }),
|
|
429
|
+
signal: AbortSignal.timeout(10_000),
|
|
430
|
+
});
|
|
431
|
+
} catch { /* best-effort */ }
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
// Test hooks
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
|
|
438
|
+
/** Wipe the session-state file; used by tests to start fresh. */
|
|
439
|
+
export async function _resetSessionState(homeOverride?: string): Promise<void> {
|
|
440
|
+
try { await writeFile(sessionStatePath(homeOverride), "{}"); } catch { /* best-effort */ }
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Prefer existsSync for callers that want to check without reading. */
|
|
444
|
+
export function _sessionStateExists(homeOverride?: string): boolean {
|
|
445
|
+
return existsSync(sessionStatePath(homeOverride));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** Exposed for tests that need to stat the jsonl (e.g. mtime assertions). */
|
|
449
|
+
export async function _jsonlStat(homeOverride?: string): Promise<{ size: number } | null> {
|
|
450
|
+
try { const st = await stat(nudgeEventsPath(homeOverride)); return { size: st.size }; } catch { return null; }
|
|
451
|
+
}
|