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,775 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* ashlr upgrade-flow.ts — terminal-native free-to-pro upgrade.
|
|
4
|
+
*
|
|
5
|
+
* Driven by the /ashlr-upgrade skill. Walks the user from free → pro/team
|
|
6
|
+
* entirely in the terminal, in under 90 seconds.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* bun run scripts/upgrade-flow.ts
|
|
10
|
+
* bun run scripts/upgrade-flow.ts --tier pro --annual
|
|
11
|
+
* bun run scripts/upgrade-flow.ts --tier team --annual --email you@example.com
|
|
12
|
+
* bun run scripts/upgrade-flow.ts --no-poll # skip activation polling (testing)
|
|
13
|
+
*
|
|
14
|
+
* Env:
|
|
15
|
+
* ASHLR_API_URL — default https://api.ashlr.ai
|
|
16
|
+
* ASHLR_PRO_TOKEN — if set, skip sign-in and go straight to checkout
|
|
17
|
+
* ASHLR_NO_BROWSER=1 — skip browser open (headless / CI / SSH)
|
|
18
|
+
*
|
|
19
|
+
* Stdout: the user-facing transcript.
|
|
20
|
+
* Exits: 0 on success or graceful timeout, 1 on fatal error.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "fs";
|
|
24
|
+
import { readFile, writeFile } from "fs/promises";
|
|
25
|
+
import { homedir } from "os";
|
|
26
|
+
import { join } from "path";
|
|
27
|
+
import { spawn } from "child_process";
|
|
28
|
+
import * as readline from "readline";
|
|
29
|
+
import { randomBytes } from "crypto";
|
|
30
|
+
import { recordNudgeClicked, maybeSyncToCloud as maybeSyncNudgeEvents } from "../servers/_nudge-events.ts";
|
|
31
|
+
import { validateProToken } from "../servers/_pro.ts";
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Config
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
const API_URL = process.env["ASHLR_API_URL"] ?? "https://api.ashlr.ai";
|
|
38
|
+
const SITE_URL = process.env["ASHLR_SITE_URL"] ?? "https://plugin.ashlr.ai";
|
|
39
|
+
const ASHLR_DIR = join(homedir(), ".ashlr");
|
|
40
|
+
const TOKEN_FILE = join(ASHLR_DIR, "pro-token");
|
|
41
|
+
const ENV_FILE = join(ASHLR_DIR, "env");
|
|
42
|
+
const NO_BROWSER = process.env["ASHLR_NO_BROWSER"] === "1";
|
|
43
|
+
|
|
44
|
+
// Poll intervals / timeouts
|
|
45
|
+
const AUTH_POLL_INTERVAL_MS = 3_000;
|
|
46
|
+
const AUTH_POLL_TIMEOUT_MS = 3 * 60 * 1_000; // 3 minutes
|
|
47
|
+
const BILLING_POLL_INTERVAL_MS = 5_000;
|
|
48
|
+
const BILLING_POLL_TIMEOUT_MS = 10 * 60 * 1_000; // 10 minutes
|
|
49
|
+
|
|
50
|
+
// Brand colors (truecolor)
|
|
51
|
+
const TTY = process.stdout.isTTY;
|
|
52
|
+
const GREEN = TTY ? "\x1b[38;2;0;200;120m" : "";
|
|
53
|
+
const BOLD = TTY ? "\x1b[1m" : "";
|
|
54
|
+
const DIM = TTY ? "\x1b[2m" : "";
|
|
55
|
+
const RESET = TTY ? "\x1b[0m" : "";
|
|
56
|
+
const CYAN = TTY ? "\x1b[38;2;100;210;255m" : "";
|
|
57
|
+
const YELLOW = TTY ? "\x1b[38;2;255;200;80m" : "";
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// CLI flags
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
interface Flags {
|
|
64
|
+
tier: "pro" | "team" | null;
|
|
65
|
+
annual: boolean;
|
|
66
|
+
email: string | null;
|
|
67
|
+
noPoll: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseArgs(): Flags {
|
|
71
|
+
const args = process.argv.slice(2);
|
|
72
|
+
const flags: Flags = { tier: null, annual: false, email: null, noPoll: false };
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < args.length; i++) {
|
|
75
|
+
const a = args[i]!;
|
|
76
|
+
if (a === "--annual") flags.annual = true;
|
|
77
|
+
if (a === "--no-poll") flags.noPoll = true;
|
|
78
|
+
if (a === "--tier" && args[i + 1]) {
|
|
79
|
+
const v = args[++i]!;
|
|
80
|
+
if (v === "pro" || v === "team") flags.tier = v;
|
|
81
|
+
}
|
|
82
|
+
if (a === "--email" && args[i + 1]) {
|
|
83
|
+
flags.email = args[++i]!;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return flags;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// UI helpers
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
function print(msg: string): void {
|
|
94
|
+
process.stdout.write(msg + "\n");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function banner(): void {
|
|
98
|
+
print("");
|
|
99
|
+
print(`${BOLD}${GREEN} ╔══════════════════════════════════╗${RESET}`);
|
|
100
|
+
print(`${BOLD}${GREEN} ║ ashlr · upgrade ║${RESET}`);
|
|
101
|
+
print(`${BOLD}${GREEN} ╚══════════════════════════════════╝${RESET}`);
|
|
102
|
+
print("");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function step(n: number, total: number, label: string): void {
|
|
106
|
+
print(`\n${CYAN}${BOLD}▬▬▬ STEP ${n}/${total}: ${label} ▬▬▬${RESET}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function ok(msg: string): void {
|
|
110
|
+
print(`${GREEN}${BOLD} ✓${RESET} ${msg}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function info(msg: string): void {
|
|
114
|
+
print(`${DIM} ${msg}${RESET}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function warn(msg: string): void {
|
|
118
|
+
print(`${YELLOW} ! ${msg}${RESET}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Animated dots on one line, returns a stopper function.
|
|
122
|
+
function startDots(prefix: string): () => void {
|
|
123
|
+
const frames = [".", "..", "..."];
|
|
124
|
+
let i = 0;
|
|
125
|
+
if (!TTY) {
|
|
126
|
+
process.stdout.write(prefix + " ");
|
|
127
|
+
return () => { process.stdout.write("\n"); };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
process.stdout.write(prefix);
|
|
131
|
+
const timer = setInterval(() => {
|
|
132
|
+
readline.clearLine(process.stdout, 0);
|
|
133
|
+
readline.cursorTo(process.stdout, 0);
|
|
134
|
+
process.stdout.write(prefix + " " + frames[i % 3]!);
|
|
135
|
+
i++;
|
|
136
|
+
}, 500);
|
|
137
|
+
|
|
138
|
+
return () => {
|
|
139
|
+
clearInterval(timer);
|
|
140
|
+
readline.clearLine(process.stdout, 0);
|
|
141
|
+
readline.cursorTo(process.stdout, 0);
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Stdin input with timeout
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
async function prompt(question: string, timeoutMs = 60_000): Promise<string> {
|
|
150
|
+
process.stdout.write(` ${BOLD}${question}${RESET} `);
|
|
151
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
|
|
152
|
+
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
const timer = setTimeout(() => {
|
|
155
|
+
rl.close();
|
|
156
|
+
reject(new Error("Input timed out"));
|
|
157
|
+
}, timeoutMs);
|
|
158
|
+
|
|
159
|
+
rl.once("line", (line) => {
|
|
160
|
+
clearTimeout(timer);
|
|
161
|
+
rl.close();
|
|
162
|
+
resolve(line.trim());
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
rl.once("close", () => {
|
|
166
|
+
clearTimeout(timer);
|
|
167
|
+
resolve("");
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Token storage
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
function ensureAshlrDir(): void {
|
|
177
|
+
if (!existsSync(ASHLR_DIR)) {
|
|
178
|
+
mkdirSync(ASHLR_DIR, { recursive: true, mode: 0o700 });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function saveToken(token: string): void {
|
|
183
|
+
ensureAshlrDir();
|
|
184
|
+
writeFileSync(TOKEN_FILE, token, { encoding: "utf8", mode: 0o600 });
|
|
185
|
+
// Try to set mode 0600 explicitly (belt-and-suspenders for older bun)
|
|
186
|
+
try { chmodSync(TOKEN_FILE, 0o600); } catch { /* best-effort */ }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function writeEnvFile(token: string): Promise<void> {
|
|
190
|
+
ensureAshlrDir();
|
|
191
|
+
let existing = "";
|
|
192
|
+
try { existing = await readFile(ENV_FILE, "utf8"); } catch { /* new file */ }
|
|
193
|
+
|
|
194
|
+
// Remove any prior ASHLR_PRO_TOKEN line, then append the fresh one
|
|
195
|
+
const lines = existing
|
|
196
|
+
.split("\n")
|
|
197
|
+
.filter((l) => !l.startsWith("export ASHLR_PRO_TOKEN="));
|
|
198
|
+
lines.push(`export ASHLR_PRO_TOKEN=${token}`);
|
|
199
|
+
|
|
200
|
+
await writeFile(ENV_FILE, lines.join("\n").trimStart() + "\n", { encoding: "utf8", mode: 0o600 });
|
|
201
|
+
try { chmodSync(ENV_FILE, 0o600); } catch { /* best-effort */ }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function readStoredToken(): string | null {
|
|
205
|
+
try {
|
|
206
|
+
const t = readFileSync(TOKEN_FILE, "utf8").trim();
|
|
207
|
+
return t || null;
|
|
208
|
+
} catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
// HTTP helpers
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
|
|
217
|
+
async function apiFetch(
|
|
218
|
+
path: string,
|
|
219
|
+
opts: { method?: string; body?: unknown; token?: string },
|
|
220
|
+
): Promise<{ ok: boolean; status: number; data: unknown }> {
|
|
221
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
222
|
+
if (opts.token) headers["Authorization"] = `Bearer ${opts.token}`;
|
|
223
|
+
|
|
224
|
+
const res = await fetch(`${API_URL}${path}`, {
|
|
225
|
+
method: opts.method ?? "GET",
|
|
226
|
+
headers,
|
|
227
|
+
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
let data: unknown;
|
|
231
|
+
try { data = await res.json(); } catch { data = {}; }
|
|
232
|
+
return { ok: res.ok, status: res.status, data };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// Browser open — cross-platform
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
async function openBrowser(url: string): Promise<void> {
|
|
240
|
+
if (NO_BROWSER) {
|
|
241
|
+
warn("ASHLR_NO_BROWSER=1 — skipping browser open.");
|
|
242
|
+
print(`\n Open this URL manually:\n\n ${BOLD}${url}${RESET}\n`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let cmd: string;
|
|
247
|
+
let args: string[];
|
|
248
|
+
|
|
249
|
+
switch (process.platform) {
|
|
250
|
+
case "darwin":
|
|
251
|
+
cmd = "open"; args = [url]; break;
|
|
252
|
+
case "win32":
|
|
253
|
+
cmd = "cmd"; args = ["/c", "start", '""', url]; break;
|
|
254
|
+
default: // linux + others
|
|
255
|
+
cmd = "xdg-open"; args = [url]; break;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const fallbackToUrl = () => {
|
|
259
|
+
warn("Could not open a browser automatically.");
|
|
260
|
+
print(`\n Open this URL manually:\n\n ${BOLD}${url}${RESET}\n`);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
|
|
265
|
+
// Attach the error listener BEFORE detaching — spawn() can reach the
|
|
266
|
+
// "spawned successfully" callback and then emit 'error' asynchronously
|
|
267
|
+
// (e.g. when the child exits with ENOENT on Windows, or xdg-open is
|
|
268
|
+
// missing on a minimal Linux). Without this listener the error would
|
|
269
|
+
// crash the process or be silently swallowed and the user would see
|
|
270
|
+
// "Opened checkout" when nothing actually opened.
|
|
271
|
+
let errored = false;
|
|
272
|
+
child.once("error", () => { errored = true; fallbackToUrl(); });
|
|
273
|
+
child.unref();
|
|
274
|
+
// Give the child time to surface immediate spawn errors before we claim
|
|
275
|
+
// success. Windows' `cmd /c start` emits ENOENT / URL-handler failures
|
|
276
|
+
// noticeably later than POSIX `open` / `xdg-open`, so we wait longer
|
|
277
|
+
// there to avoid a false "Opened checkout" message.
|
|
278
|
+
const settleMs = process.platform === "win32" ? 500 : 50;
|
|
279
|
+
await new Promise((resolve) => setTimeout(resolve, settleMs));
|
|
280
|
+
if (!errored) {
|
|
281
|
+
ok("Opened checkout in your browser. Complete payment to activate Pro.");
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
fallbackToUrl();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// Step 1 — Check current tier
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
|
|
292
|
+
async function checkCurrentTier(): Promise<{ token: string; tier: string } | null> {
|
|
293
|
+
const envToken = process.env["ASHLR_PRO_TOKEN"];
|
|
294
|
+
const storedToken = readStoredToken();
|
|
295
|
+
const token = envToken ?? storedToken;
|
|
296
|
+
if (!token) return null;
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const { ok: ok_, data } = await apiFetch("/billing/status", { token });
|
|
300
|
+
if (ok_) {
|
|
301
|
+
const d = data as { tier?: string };
|
|
302
|
+
return { token, tier: d.tier ?? "free" };
|
|
303
|
+
}
|
|
304
|
+
} catch {
|
|
305
|
+
// network error — treat as unauthenticated
|
|
306
|
+
}
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// Step 2 — Sign in (GitHub or magic-link)
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Pure, injectable polling function for GitHub OAuth session completion.
|
|
316
|
+
* Exported for unit testing — does not touch UI, browser, or file system.
|
|
317
|
+
*/
|
|
318
|
+
export async function pollAuthStatusBySid(
|
|
319
|
+
sid: string,
|
|
320
|
+
opts: {
|
|
321
|
+
apiUrl: string;
|
|
322
|
+
timeoutMs: number;
|
|
323
|
+
intervalMs: number;
|
|
324
|
+
fetch?: typeof globalThis.fetch;
|
|
325
|
+
},
|
|
326
|
+
): Promise<{ apiToken: string }> {
|
|
327
|
+
const fetcher = opts.fetch ?? globalThis.fetch;
|
|
328
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
329
|
+
|
|
330
|
+
while (Date.now() < deadline) {
|
|
331
|
+
await sleep(opts.intervalMs);
|
|
332
|
+
|
|
333
|
+
const res = await fetcher(
|
|
334
|
+
`${opts.apiUrl}/auth/status?session=${encodeURIComponent(sid)}`,
|
|
335
|
+
{ headers: { "Content-Type": "application/json" } },
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
if (!res.ok) {
|
|
339
|
+
// 4xx → immediate throw, not retried
|
|
340
|
+
let body: unknown;
|
|
341
|
+
try { body = await res.json(); } catch { body = {}; }
|
|
342
|
+
const msg = (body as { error?: string }).error ?? `auth/status returned HTTP ${res.status}`;
|
|
343
|
+
throw new Error(msg);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
let data: unknown;
|
|
347
|
+
try { data = await res.json(); } catch { data = {}; }
|
|
348
|
+
const d = data as { ready?: boolean; apiToken?: string };
|
|
349
|
+
if (d.ready && d.apiToken) {
|
|
350
|
+
return { apiToken: d.apiToken };
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
throw new Error(
|
|
355
|
+
"GitHub sign-in timed out. Complete the browser flow and run /ashlr-upgrade again.",
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function signInWithGitHub(): Promise<string> {
|
|
360
|
+
const sid = randomBytes(16).toString("hex");
|
|
361
|
+
const url = `${SITE_URL}/auth/github?sid=${sid}`;
|
|
362
|
+
|
|
363
|
+
await openBrowser(url);
|
|
364
|
+
ok("Opened GitHub sign-in in your browser. Approve access to complete.");
|
|
365
|
+
info("Waiting for GitHub authorisation (up to 3 minutes)...");
|
|
366
|
+
print("");
|
|
367
|
+
|
|
368
|
+
const stopDots = startDots(" Waiting");
|
|
369
|
+
try {
|
|
370
|
+
const { apiToken } = await pollAuthStatusBySid(sid, {
|
|
371
|
+
apiUrl: API_URL,
|
|
372
|
+
timeoutMs: AUTH_POLL_TIMEOUT_MS,
|
|
373
|
+
intervalMs: AUTH_POLL_INTERVAL_MS,
|
|
374
|
+
});
|
|
375
|
+
stopDots();
|
|
376
|
+
|
|
377
|
+
// Best-effort: display github login. If /auth/whoami doesn't exist yet, skip.
|
|
378
|
+
try {
|
|
379
|
+
const whoami = await apiFetch("/auth/whoami", { token: apiToken });
|
|
380
|
+
if (whoami.ok) {
|
|
381
|
+
const d = whoami.data as { login?: string };
|
|
382
|
+
if (d.login) ok(`Signed in as @${d.login}`);
|
|
383
|
+
else ok("Signed in ✓");
|
|
384
|
+
} else {
|
|
385
|
+
ok("Signed in ✓");
|
|
386
|
+
}
|
|
387
|
+
} catch {
|
|
388
|
+
ok("Signed in ✓");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return apiToken;
|
|
392
|
+
} catch (err) {
|
|
393
|
+
stopDots();
|
|
394
|
+
throw err;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function signInWithMagicLink(emailArg: string | null): Promise<string> {
|
|
399
|
+
let email = emailArg;
|
|
400
|
+
if (!email) {
|
|
401
|
+
try {
|
|
402
|
+
email = await prompt("[ASHLR_PROMPT: Email to sign in?]");
|
|
403
|
+
} catch {
|
|
404
|
+
throw new Error("No email provided.");
|
|
405
|
+
}
|
|
406
|
+
} else {
|
|
407
|
+
print(` Using email: ${BOLD}${email}${RESET}`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (!email || !email.includes("@")) {
|
|
411
|
+
throw new Error("Invalid email address.");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// POST /auth/send
|
|
415
|
+
const sendRes = await apiFetch("/auth/send", { method: "POST", body: { email } });
|
|
416
|
+
if (!sendRes.ok) {
|
|
417
|
+
const d = sendRes.data as { error?: string };
|
|
418
|
+
throw new Error(d.error ?? `Failed to send magic link (HTTP ${sendRes.status})`);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
ok(`Magic link sent to ${BOLD}${email}${RESET}. Check your inbox — click the link to continue.`);
|
|
422
|
+
info("Waiting for you to click the link (up to 3 minutes)...");
|
|
423
|
+
print("");
|
|
424
|
+
|
|
425
|
+
// Poll /auth/status
|
|
426
|
+
const stopDots = startDots(" Waiting");
|
|
427
|
+
const deadline = Date.now() + AUTH_POLL_TIMEOUT_MS;
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
while (Date.now() < deadline) {
|
|
431
|
+
await sleep(AUTH_POLL_INTERVAL_MS);
|
|
432
|
+
try {
|
|
433
|
+
const res = await apiFetch(`/auth/status?email=${encodeURIComponent(email)}`, {});
|
|
434
|
+
if (res.ok) {
|
|
435
|
+
const d = res.data as { ready?: boolean; apiToken?: string };
|
|
436
|
+
if (d.ready && d.apiToken) {
|
|
437
|
+
stopDots();
|
|
438
|
+
ok("Sign-in confirmed.");
|
|
439
|
+
return d.apiToken;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
} catch {
|
|
443
|
+
// network hiccup — keep polling
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
} finally {
|
|
447
|
+
stopDots();
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
throw new Error(
|
|
451
|
+
"Sign-in timed out. Check your email and try again, or run /ashlr-upgrade once you've clicked the link.",
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/** Present auth-method menu and route to the right flow. */
|
|
456
|
+
async function pickAuthMethod(emailFlag: string | null): Promise<string> {
|
|
457
|
+
print("");
|
|
458
|
+
print(`${DIM} No active Pro token found. Let's sign you in first.${RESET}`);
|
|
459
|
+
|
|
460
|
+
// --email flag → skip menu, go straight to magic-link (back-compat)
|
|
461
|
+
if (emailFlag !== null) {
|
|
462
|
+
return signInWithMagicLink(emailFlag);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
print("");
|
|
466
|
+
print(` Sign in with:`);
|
|
467
|
+
print(` ${BOLD}1)${RESET} GitHub ${DIM}(recommended)${RESET} — one click in your browser`);
|
|
468
|
+
print(` ${BOLD}2)${RESET} Email magic-link`);
|
|
469
|
+
print(` ${DIM}Choice [1]:${RESET}`);
|
|
470
|
+
print("");
|
|
471
|
+
|
|
472
|
+
let choice: string;
|
|
473
|
+
try {
|
|
474
|
+
choice = await prompt("[ASHLR_PROMPT: Choice [1]:]");
|
|
475
|
+
} catch {
|
|
476
|
+
choice = "1";
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (choice === "2") {
|
|
480
|
+
return signInWithMagicLink(null);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Default: GitHub (Enter or "1")
|
|
484
|
+
return signInWithGitHub();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ---------------------------------------------------------------------------
|
|
488
|
+
// Step 3 — Pick a tier
|
|
489
|
+
// ---------------------------------------------------------------------------
|
|
490
|
+
|
|
491
|
+
type TierKey = "pro" | "pro-annual" | "team" | "team-annual";
|
|
492
|
+
|
|
493
|
+
interface TierOption {
|
|
494
|
+
key: TierKey;
|
|
495
|
+
label: string;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const TIER_OPTIONS: TierOption[] = [
|
|
499
|
+
{ key: "pro", label: "Pro · $12/mo" },
|
|
500
|
+
{ key: "pro-annual", label: "Pro · $120/yr (save 17%)" },
|
|
501
|
+
{ key: "team", label: "Team · $24/user/mo" },
|
|
502
|
+
{ key: "team-annual", label: "Team · $240/user/yr (save 17%)" },
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
async function pickTier(flags: Flags): Promise<TierKey> {
|
|
506
|
+
// If fully specified by flags, skip interactive prompt
|
|
507
|
+
if (flags.tier) {
|
|
508
|
+
const key: TierKey = flags.annual
|
|
509
|
+
? `${flags.tier}-annual`
|
|
510
|
+
: flags.tier;
|
|
511
|
+
const opt = TIER_OPTIONS.find((o) => o.key === key);
|
|
512
|
+
if (opt) {
|
|
513
|
+
info(`Using tier: ${opt.label}`);
|
|
514
|
+
return key;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
print("");
|
|
519
|
+
TIER_OPTIONS.forEach((o, i) => {
|
|
520
|
+
const isDefault = i === 0;
|
|
521
|
+
print(` ${BOLD}${i + 1}${RESET}) ${o.label}${isDefault ? DIM + " [default]" + RESET : ""}`);
|
|
522
|
+
});
|
|
523
|
+
print("");
|
|
524
|
+
|
|
525
|
+
let choice: string;
|
|
526
|
+
try {
|
|
527
|
+
choice = await prompt("[ASHLR_PROMPT: Choose a plan (1-4, default 1):]");
|
|
528
|
+
} catch {
|
|
529
|
+
choice = "1";
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const idx = parseInt(choice || "1", 10) - 1;
|
|
533
|
+
if (idx < 0 || idx >= TIER_OPTIONS.length) {
|
|
534
|
+
info("Invalid selection — defaulting to Pro monthly.");
|
|
535
|
+
return "pro";
|
|
536
|
+
}
|
|
537
|
+
return TIER_OPTIONS[idx]!.key;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// ---------------------------------------------------------------------------
|
|
541
|
+
// Step 4 — Open checkout
|
|
542
|
+
// ---------------------------------------------------------------------------
|
|
543
|
+
|
|
544
|
+
async function openCheckout(tier: TierKey, token: string): Promise<void> {
|
|
545
|
+
const isTeam = tier.startsWith("team");
|
|
546
|
+
|
|
547
|
+
let seats = 1;
|
|
548
|
+
if (isTeam) {
|
|
549
|
+
let seatsStr: string;
|
|
550
|
+
try {
|
|
551
|
+
seatsStr = await prompt("[ASHLR_PROMPT: How many seats (default 3):]");
|
|
552
|
+
} catch {
|
|
553
|
+
seatsStr = "3";
|
|
554
|
+
}
|
|
555
|
+
seats = Math.max(1, parseInt(seatsStr || "3", 10) || 3);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const res = await apiFetch("/billing/checkout", {
|
|
559
|
+
method: "POST",
|
|
560
|
+
body: { tier, seats },
|
|
561
|
+
token,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
if (!res.ok) {
|
|
565
|
+
const d = res.data as { error?: string };
|
|
566
|
+
throw new Error(d.error ?? `Checkout failed (HTTP ${res.status})`);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const d = res.data as { url?: string; trial?: { days?: number } | null };
|
|
570
|
+
if (!d.url) throw new Error("No checkout URL returned from server.");
|
|
571
|
+
|
|
572
|
+
if (d.trial?.days && d.trial.days > 0) {
|
|
573
|
+
info(
|
|
574
|
+
`First-time upgrade: ${d.trial.days}-day free trial included. ` +
|
|
575
|
+
`No card charged until day ${d.trial.days}; cancel anytime before then.`,
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
await openBrowser(d.url);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ---------------------------------------------------------------------------
|
|
583
|
+
// Step 5 — Poll for activation
|
|
584
|
+
// ---------------------------------------------------------------------------
|
|
585
|
+
|
|
586
|
+
async function pollActivation(token: string): Promise<boolean> {
|
|
587
|
+
info("Polling for payment confirmation (up to 10 minutes)...");
|
|
588
|
+
const stopDots = startDots(" Waiting");
|
|
589
|
+
const deadline = Date.now() + BILLING_POLL_TIMEOUT_MS;
|
|
590
|
+
|
|
591
|
+
try {
|
|
592
|
+
while (Date.now() < deadline) {
|
|
593
|
+
await sleep(BILLING_POLL_INTERVAL_MS);
|
|
594
|
+
try {
|
|
595
|
+
const res = await apiFetch("/billing/status", { token });
|
|
596
|
+
if (res.ok) {
|
|
597
|
+
const d = res.data as { tier?: string };
|
|
598
|
+
if (d.tier === "pro" || d.tier === "team") {
|
|
599
|
+
stopDots();
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
} catch {
|
|
604
|
+
// keep polling
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
} finally {
|
|
608
|
+
stopDots();
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// ---------------------------------------------------------------------------
|
|
615
|
+
// Helpers
|
|
616
|
+
// ---------------------------------------------------------------------------
|
|
617
|
+
|
|
618
|
+
function sleep(ms: number): Promise<void> {
|
|
619
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function printActivationSuccess(
|
|
623
|
+
tier: string,
|
|
624
|
+
opts: { trialEndsAt?: string | null } = {},
|
|
625
|
+
): void {
|
|
626
|
+
const tierLabel = tier.charAt(0).toUpperCase() + tier.slice(1);
|
|
627
|
+
const trialSuffix = opts.trialEndsAt
|
|
628
|
+
? ` (trial ends ${new Date(opts.trialEndsAt).toLocaleDateString()})`
|
|
629
|
+
: "";
|
|
630
|
+
print("");
|
|
631
|
+
print(`${GREEN}${BOLD} ✓ Pro activated! Plan: ${tierLabel}${trialSuffix}${RESET}`);
|
|
632
|
+
print(` Your API token is saved locally. All Pro features are unlocked.`);
|
|
633
|
+
print("");
|
|
634
|
+
print(` What just changed:`);
|
|
635
|
+
print(` - Hosted LLM summarizer — Ollama not required`);
|
|
636
|
+
print(` - Cross-machine stats sync`);
|
|
637
|
+
print(` - Leaderboard participation`);
|
|
638
|
+
print(` - Priority support at support@ashlr.ai`);
|
|
639
|
+
print("");
|
|
640
|
+
print(`${DIM} Next: run /ashlr-dashboard to see your usage.${RESET}`);
|
|
641
|
+
print("");
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// ---------------------------------------------------------------------------
|
|
645
|
+
// Main flow
|
|
646
|
+
// ---------------------------------------------------------------------------
|
|
647
|
+
|
|
648
|
+
async function main(): Promise<number> {
|
|
649
|
+
const flags = parseArgs();
|
|
650
|
+
const totalSteps = 5;
|
|
651
|
+
|
|
652
|
+
// Record the click as early as possible — before any network I/O — so even
|
|
653
|
+
// an aborted run still counts toward conversion. Correlates to the most
|
|
654
|
+
// recent nudge_shown in the same session (within 30 min). Fire-and-forget.
|
|
655
|
+
void recordNudgeClicked({}).catch(() => {});
|
|
656
|
+
maybeSyncNudgeEvents();
|
|
657
|
+
|
|
658
|
+
banner();
|
|
659
|
+
|
|
660
|
+
// ── Step 1: Check current tier ──────────────────────────────────────────
|
|
661
|
+
step(1, totalSteps, "Checking current tier");
|
|
662
|
+
|
|
663
|
+
let token: string | null = null;
|
|
664
|
+
let currentTier = "free";
|
|
665
|
+
|
|
666
|
+
const existing = await checkCurrentTier();
|
|
667
|
+
if (existing) {
|
|
668
|
+
token = existing.token;
|
|
669
|
+
currentTier = existing.tier;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (currentTier === "pro" || currentTier === "team") {
|
|
673
|
+
ok(`You're already on ${currentTier.charAt(0).toUpperCase() + currentTier.slice(1)}.`);
|
|
674
|
+
info("Run /ashlr-dashboard to see usage.");
|
|
675
|
+
return 0;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
ok(currentTier === "free" && token ? "Authenticated as free user." : "Not signed in yet.");
|
|
679
|
+
|
|
680
|
+
// ── Step 2: Sign in if needed ────────────────────────────────────────────
|
|
681
|
+
if (!token) {
|
|
682
|
+
step(2, totalSteps, "Sign in");
|
|
683
|
+
|
|
684
|
+
try {
|
|
685
|
+
token = await pickAuthMethod(flags.email);
|
|
686
|
+
} catch (err) {
|
|
687
|
+
warn(err instanceof Error ? err.message : String(err));
|
|
688
|
+
return 1;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Persist token
|
|
692
|
+
saveToken(token);
|
|
693
|
+
await writeEnvFile(token);
|
|
694
|
+
process.env["ASHLR_PRO_TOKEN"] = token;
|
|
695
|
+
ok("Token saved to ~/.ashlr/pro-token and ~/.ashlr/env (auto-loaded on next session).");
|
|
696
|
+
} else {
|
|
697
|
+
step(2, totalSteps, "Sign in");
|
|
698
|
+
ok("Already authenticated — skipping sign-in.");
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// ── Step 3: Pick a tier ──────────────────────────────────────────────────
|
|
702
|
+
step(3, totalSteps, "Choose your plan");
|
|
703
|
+
|
|
704
|
+
let chosenTier: TierKey;
|
|
705
|
+
try {
|
|
706
|
+
chosenTier = await pickTier(flags);
|
|
707
|
+
} catch (err) {
|
|
708
|
+
warn(err instanceof Error ? err.message : String(err));
|
|
709
|
+
return 1;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
ok(`Selected: ${TIER_OPTIONS.find((o) => o.key === chosenTier)?.label ?? chosenTier}`);
|
|
713
|
+
|
|
714
|
+
// ── Step 4: Open checkout ────────────────────────────────────────────────
|
|
715
|
+
step(4, totalSteps, "Open Stripe checkout");
|
|
716
|
+
|
|
717
|
+
try {
|
|
718
|
+
await openCheckout(chosenTier, token);
|
|
719
|
+
} catch (err) {
|
|
720
|
+
warn(err instanceof Error ? err.message : String(err));
|
|
721
|
+
return 1;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// ── Step 5: Poll for activation ──────────────────────────────────────────
|
|
725
|
+
if (flags.noPoll) {
|
|
726
|
+
step(5, totalSteps, "Activate Pro");
|
|
727
|
+
info("--no-poll set — skipping activation check.");
|
|
728
|
+
return 0;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
step(5, totalSteps, "Waiting for payment confirmation");
|
|
732
|
+
|
|
733
|
+
const activated = await pollActivation(token);
|
|
734
|
+
|
|
735
|
+
if (activated) {
|
|
736
|
+
// Validate the token to get the real tier + trial info, and seed the cache
|
|
737
|
+
// so the user doesn't have to wait for a network round-trip on next launch.
|
|
738
|
+
let finalTier = "pro";
|
|
739
|
+
let trialEndsAt: string | null = null;
|
|
740
|
+
try {
|
|
741
|
+
const validation = await validateProToken();
|
|
742
|
+
if (validation.valid && validation.plan) {
|
|
743
|
+
finalTier = validation.plan;
|
|
744
|
+
trialEndsAt = validation.trialEndsAt ?? null;
|
|
745
|
+
}
|
|
746
|
+
} catch { /* fall back to billing/status */ }
|
|
747
|
+
|
|
748
|
+
if (!trialEndsAt) {
|
|
749
|
+
try {
|
|
750
|
+
const r = await apiFetch("/billing/status", { token });
|
|
751
|
+
if (r.ok) {
|
|
752
|
+
const d = r.data as { tier?: string; trialEndsAt?: string | null };
|
|
753
|
+
finalTier = d.tier ?? finalTier;
|
|
754
|
+
trialEndsAt = d.trialEndsAt ?? null;
|
|
755
|
+
}
|
|
756
|
+
} catch { /* use defaults */ }
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
printActivationSuccess(finalTier, { trialEndsAt });
|
|
760
|
+
} else {
|
|
761
|
+
print("");
|
|
762
|
+
warn("Haven't detected payment yet.");
|
|
763
|
+
info("Once you complete checkout, run /ashlr-upgrade again to verify and activate.");
|
|
764
|
+
print("");
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return 0;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (import.meta.main) {
|
|
771
|
+
main().then((code) => process.exit(code)).catch((err) => {
|
|
772
|
+
process.stderr.write(`upgrade-flow: fatal — ${err instanceof Error ? err.message : String(err)}\n`);
|
|
773
|
+
process.exit(1);
|
|
774
|
+
});
|
|
775
|
+
}
|