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.
Files changed (499) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude-plugin/marketplace.json +39 -0
  3. package/.claude-plugin/plugin.json +36 -0
  4. package/.codex/agents/README.md +9 -0
  5. package/.codex/agents/ashlr-explorer.md +36 -0
  6. package/.codex/agents/ashlr-worker.md +38 -0
  7. package/.codex-plugin/plugin.json +45 -0
  8. package/.mcp.json +14 -0
  9. package/LICENSE +21 -0
  10. package/README.md +311 -0
  11. package/agents/ashlr-code.md +69 -0
  12. package/agents/ashlr-explore.md +44 -0
  13. package/agents/ashlr-plan.md +54 -0
  14. package/bench/fixtures/notebook-large.ipynb +660 -0
  15. package/bench/fixtures/notebook-small.ipynb +39 -0
  16. package/bench/fixtures/tasklist-closed.json +62 -0
  17. package/bench/fixtures/tasklist-large-mixed.json +27 -0
  18. package/bench/fixtures/tasklist-sprint-open.json +79 -0
  19. package/bench/fixtures/websearch-bun-runtime.json +32 -0
  20. package/bench/fixtures/websearch-react-hooks-patterns.json +32 -0
  21. package/bench/fixtures/websearch-typescript-generics.json +38 -0
  22. package/bench/refs/node-sdk/.refrev +1 -0
  23. package/bench/refs/node-sdk/README.md +17 -0
  24. package/bench/refs/node-sdk/src/agent/agent.ts +163 -0
  25. package/bench/refs/node-sdk/src/agent/create-agent-ui-stream.ts +83 -0
  26. package/bench/refs/node-sdk/src/agent/tool-loop-agent.ts +238 -0
  27. package/bench/refs/node-sdk/src/embed/embed-many.ts +409 -0
  28. package/bench/refs/node-sdk/src/embed/embed-result.ts +50 -0
  29. package/bench/refs/node-sdk/src/embed/embed.ts +257 -0
  30. package/bench/refs/node-sdk/src/error/invalid-argument-error.ts +34 -0
  31. package/bench/refs/node-sdk/src/error/invalid-tool-input-error.ts +33 -0
  32. package/bench/refs/node-sdk/src/error/no-object-generated-error.ts +70 -0
  33. package/bench/refs/node-sdk/src/generate-image/generate-image-result.ts +42 -0
  34. package/bench/refs/node-sdk/src/generate-image/generate-image.ts +362 -0
  35. package/bench/refs/node-sdk/src/generate-object/generate-object-result.ts +67 -0
  36. package/bench/refs/node-sdk/src/generate-object/generate-object.ts +501 -0
  37. package/bench/refs/node-sdk/src/generate-object/inject-json-instruction.ts +30 -0
  38. package/bench/refs/node-sdk/src/generate-object/output-strategy.ts +415 -0
  39. package/bench/refs/node-sdk/src/generate-object/repair-text.ts +12 -0
  40. package/bench/refs/node-sdk/src/generate-object/stream-object.ts +960 -0
  41. package/bench/refs/node-sdk/src/generate-speech/generate-speech.ts +191 -0
  42. package/bench/refs/node-sdk/src/generate-text/generate-text.ts +1484 -0
  43. package/bench/refs/node-sdk/src/generate-text/stream-text.ts +2708 -0
  44. package/bench/refs/node-sdk/src/generate-text/tool-call.ts +59 -0
  45. package/bench/refs/node-sdk/src/generate-text/tool-result.ts +36 -0
  46. package/bench/refs/node-sdk/src/telemetry/create-telemetry-dispatcher.ts +146 -0
  47. package/bench/refs/node-sdk/src/telemetry/telemetry-registry.ts +15 -0
  48. package/bench/refs/node-sdk/src/telemetry/telemetry.ts +223 -0
  49. package/bench/refs/node-sdk/src/util/async-iterable-stream.ts +94 -0
  50. package/bench/refs/node-sdk/src/util/consume-stream.ts +31 -0
  51. package/bench/refs/node-sdk/src/util/create-stitchable-stream.ts +112 -0
  52. package/bench/refs/node-sdk/src/util/download/download.ts +74 -0
  53. package/bench/refs/node-sdk/src/util/error-handler.ts +1 -0
  54. package/bench/refs/node-sdk/src/util/fix-json.ts +401 -0
  55. package/bench/refs/node-sdk/src/util/merge-objects.ts +79 -0
  56. package/bench/refs/python-lib/.refrev +1 -0
  57. package/bench/refs/python-lib/README.md +18 -0
  58. package/bench/refs/python-lib/pandas/core/_numba/executor.py +249 -0
  59. package/bench/refs/python-lib/pandas/core/_numba/extensions.py +586 -0
  60. package/bench/refs/python-lib/pandas/core/_numba/kernels/mean_.py +198 -0
  61. package/bench/refs/python-lib/pandas/core/_numba/kernels/min_max_.py +178 -0
  62. package/bench/refs/python-lib/pandas/core/_numba/kernels/sum_.py +255 -0
  63. package/bench/refs/python-lib/pandas/core/_numba/kernels/var_.py +251 -0
  64. package/bench/refs/python-lib/pandas/core/accessor.py +520 -0
  65. package/bench/refs/python-lib/pandas/core/api.py +138 -0
  66. package/bench/refs/python-lib/pandas/core/array_algos/masked_accumulations.py +97 -0
  67. package/bench/refs/python-lib/pandas/core/array_algos/masked_reductions.py +252 -0
  68. package/bench/refs/python-lib/pandas/core/array_algos/putmask.py +150 -0
  69. package/bench/refs/python-lib/pandas/core/array_algos/quantile.py +232 -0
  70. package/bench/refs/python-lib/pandas/core/array_algos/replace.py +159 -0
  71. package/bench/refs/python-lib/pandas/core/array_algos/take.py +609 -0
  72. package/bench/refs/python-lib/pandas/core/arraylike.py +534 -0
  73. package/bench/refs/python-lib/pandas/core/arrays/_arrow_string_mixins.py +434 -0
  74. package/bench/refs/python-lib/pandas/core/arrays/_mixins.py +630 -0
  75. package/bench/refs/python-lib/pandas/core/arrays/_ranges.py +276 -0
  76. package/bench/refs/python-lib/pandas/core/arrays/_utils.py +77 -0
  77. package/bench/refs/python-lib/pandas/core/arrays/arrow/accessors.py +521 -0
  78. package/bench/refs/python-lib/pandas/core/arrays/arrow/extension_types.py +174 -0
  79. package/bench/refs/python-lib/pandas/core/arrays/boolean.py +454 -0
  80. package/bench/refs/python-lib/pandas/core/arrays/floating.py +192 -0
  81. package/bench/refs/python-lib/pandas/core/arrays/integer.py +296 -0
  82. package/bench/refs/python-lib/pandas/core/arrays/masked.py +2140 -0
  83. package/bench/refs/python-lib/pandas/core/arrays/numeric.py +305 -0
  84. package/bench/refs/python-lib/pandas/core/arrays/numpy_.py +701 -0
  85. package/bench/refs/python-lib/pandas/core/arrays/period.py +1586 -0
  86. package/bench/refs/python-lib/pandas/core/arrays/sparse/accessor.py +505 -0
  87. package/bench/refs/python-lib/pandas/core/arrays/sparse/scipy_sparse.py +208 -0
  88. package/bench/refs/rust-project/.refrev +1 -0
  89. package/bench/refs/rust-project/README.md +17 -0
  90. package/bench/refs/rust-project/tokio/src/doc/os.rs +68 -0
  91. package/bench/refs/rust-project/tokio/src/fs/dir_builder.rs +129 -0
  92. package/bench/refs/rust-project/tokio/src/fs/file.rs +1113 -0
  93. package/bench/refs/rust-project/tokio/src/fs/mocks.rs +176 -0
  94. package/bench/refs/rust-project/tokio/src/fs/mod.rs +324 -0
  95. package/bench/refs/rust-project/tokio/src/fs/open_options.rs +856 -0
  96. package/bench/refs/rust-project/tokio/src/fs/read.rs +80 -0
  97. package/bench/refs/rust-project/tokio/src/fs/read_dir.rs +357 -0
  98. package/bench/refs/rust-project/tokio/src/fs/write.rs +99 -0
  99. package/bench/refs/rust-project/tokio/src/future/maybe_done.rs +123 -0
  100. package/bench/refs/rust-project/tokio/src/future/try_join.rs +82 -0
  101. package/bench/refs/rust-project/tokio/src/io/async_buf_read.rs +117 -0
  102. package/bench/refs/rust-project/tokio/src/io/async_read.rs +133 -0
  103. package/bench/refs/rust-project/tokio/src/io/async_seek.rs +96 -0
  104. package/bench/refs/rust-project/tokio/src/io/async_write.rs +402 -0
  105. package/bench/refs/rust-project/tokio/src/io/blocking.rs +339 -0
  106. package/bench/refs/rust-project/tokio/src/io/interest.rs +345 -0
  107. package/bench/refs/rust-project/tokio/src/io/join.rs +130 -0
  108. package/bench/refs/rust-project/tokio/src/io/mod.rs +308 -0
  109. package/bench/refs/rust-project/tokio/src/io/poll_evented.rs +319 -0
  110. package/bench/refs/rust-project/tokio/src/io/read_buf.rs +344 -0
  111. package/bench/refs/rust-project/tokio/src/io/ready.rs +322 -0
  112. package/bench/refs/rust-project/tokio/src/io/split.rs +166 -0
  113. package/bench/refs/rust-project/tokio/src/io/stderr.rs +148 -0
  114. package/bench/refs/rust-project/tokio/src/io/stdin.rs +98 -0
  115. package/bench/refs/rust-project/tokio/src/io/stdio_common.rs +222 -0
  116. package/bench/refs/rust-project/tokio/src/io/stdout.rs +197 -0
  117. package/bench/refs/rust-project/tokio/src/io/util/buf_reader.rs +311 -0
  118. package/bench/refs/rust-project/tokio/src/io/util/buf_stream.rs +207 -0
  119. package/bench/refs/rust-project/tokio/src/io/util/buf_writer.rs +310 -0
  120. package/bench/refs/rust-project/tokio/src/io/util/chain.rs +144 -0
  121. package/commands/_spawn-patterns.json +32 -0
  122. package/commands/ashlr-allow.md +21 -0
  123. package/commands/ashlr-benchmark.md +53 -0
  124. package/commands/ashlr-brief.md +97 -0
  125. package/commands/ashlr-budget.md +76 -0
  126. package/commands/ashlr-compact.md +54 -0
  127. package/commands/ashlr-dashboard.md +37 -0
  128. package/commands/ashlr-doctor.md +56 -0
  129. package/commands/ashlr-eco-mode.md +92 -0
  130. package/commands/ashlr-genome-init.md +24 -0
  131. package/commands/ashlr-genome-keygen.md +36 -0
  132. package/commands/ashlr-genome-loop.md +118 -0
  133. package/commands/ashlr-genome-push.md +36 -0
  134. package/commands/ashlr-genome-rewrap.md +48 -0
  135. package/commands/ashlr-genome-team-init.md +67 -0
  136. package/commands/ashlr-handoff.md +23 -0
  137. package/commands/ashlr-help.md +86 -0
  138. package/commands/ashlr-hook-timings.md +30 -0
  139. package/commands/ashlr-leaderboard.md +50 -0
  140. package/commands/ashlr-ollama-setup.md +39 -0
  141. package/commands/ashlr-orchestrate-status.md +72 -0
  142. package/commands/ashlr-orchestrate.md +83 -0
  143. package/commands/ashlr-parallelize.md +63 -0
  144. package/commands/ashlr-report-crash.md +33 -0
  145. package/commands/ashlr-resume.md +72 -0
  146. package/commands/ashlr-savings.md +24 -0
  147. package/commands/ashlr-settings.md +50 -0
  148. package/commands/ashlr-spawn.md +61 -0
  149. package/commands/ashlr-start.md +60 -0
  150. package/commands/ashlr-status.md +64 -0
  151. package/commands/ashlr-team-invite.md +47 -0
  152. package/commands/ashlr-tier.md +122 -0
  153. package/commands/ashlr-tour.md +60 -0
  154. package/commands/ashlr-update.md +155 -0
  155. package/commands/ashlr-upgrade.md +96 -0
  156. package/commands/goal.md +58 -0
  157. package/commands/loop.md +40 -0
  158. package/docs/CNAME +1 -0
  159. package/docs/PRO_TIER.md +104 -0
  160. package/docs/README.md +21 -0
  161. package/docs/architecture.md +415 -0
  162. package/docs/ashlr-pipe.md +161 -0
  163. package/docs/assets/logo.svg +19 -0
  164. package/docs/assets/og.png +0 -0
  165. package/docs/assets/og.svg +41 -0
  166. package/docs/benchmarks-v2.json +355 -0
  167. package/docs/benchmarks.json +102 -0
  168. package/docs/benchmarks.md +211 -0
  169. package/docs/billing.md +185 -0
  170. package/docs/cloud-genome.md +155 -0
  171. package/docs/competitive-wozcode.md +92 -0
  172. package/docs/deploy.md +325 -0
  173. package/docs/email-templates.md +113 -0
  174. package/docs/embed-tuning.md +103 -0
  175. package/docs/genome.md +120 -0
  176. package/docs/github-oauth-onboarding.md +127 -0
  177. package/docs/hero-video-script.md +129 -0
  178. package/docs/index.html +2262 -0
  179. package/docs/install-prompt.md +78 -0
  180. package/docs/install-windows.md +132 -0
  181. package/docs/install.ps1 +171 -0
  182. package/docs/install.sh +146 -0
  183. package/docs/launch/README.md +114 -0
  184. package/docs/launch/hn.md +49 -0
  185. package/docs/launch/reddit.md +45 -0
  186. package/docs/launch/x.md +65 -0
  187. package/docs/launch-post.md +112 -0
  188. package/docs/legal.md +78 -0
  189. package/docs/llm-providers.md +99 -0
  190. package/docs/multi-host-mcp.md +217 -0
  191. package/docs/multi-turn-architecture.md +108 -0
  192. package/docs/operations/deployment.md +108 -0
  193. package/docs/operations/error-monitoring.md +70 -0
  194. package/docs/operations/weekly-digest.md +70 -0
  195. package/docs/operations.md +494 -0
  196. package/docs/platform-support.md +133 -0
  197. package/docs/policy-packs.md +188 -0
  198. package/docs/press-kit/fact-sheet.md +78 -0
  199. package/docs/press-kit/founder-bio.md +3 -0
  200. package/docs/press-kit/logo.svg +33 -0
  201. package/docs/press-kit/screenshots.md +146 -0
  202. package/docs/pricing.md +251 -0
  203. package/docs/pro-backend-architecture.md +523 -0
  204. package/docs/producthunt.md +52 -0
  205. package/docs/router-migration.md +110 -0
  206. package/docs/session-log-schema.md +68 -0
  207. package/docs/social.md +95 -0
  208. package/docs/team-genome.md +528 -0
  209. package/docs/telemetry.md +194 -0
  210. package/docs/test-isolation.md +100 -0
  211. package/docs/upgrade.md +159 -0
  212. package/hooks/_hook-errors.ts +87 -0
  213. package/hooks/_hook-perf-emit.ts +113 -0
  214. package/hooks/_hook-timing-ledger.ts +1133 -0
  215. package/hooks/_nudge-throttle.ts +127 -0
  216. package/hooks/_recent-blocks.ts +127 -0
  217. package/hooks/audit-upload.ts +86 -0
  218. package/hooks/codex-hooks.json +171 -0
  219. package/hooks/commit-attribution.ts +179 -0
  220. package/hooks/edit-batching-nudge.ts +154 -0
  221. package/hooks/genome-scribe-hook.ts +219 -0
  222. package/hooks/hooks.json +294 -0
  223. package/hooks/orient-nudge-hook.ts +242 -0
  224. package/hooks/policy-enforce.ts +214 -0
  225. package/hooks/post-tool-use-embedding.ts +93 -0
  226. package/hooks/post-tool-use-genome.sh +37 -0
  227. package/hooks/post-tool-use-genome.ts +69 -0
  228. package/hooks/posttooluse-correlate.ts +122 -0
  229. package/hooks/posttooluse-dedupe-output.ts +241 -0
  230. package/hooks/posttooluse-genome-refresh.ts +223 -0
  231. package/hooks/posttooluse-native-nudge.ts +205 -0
  232. package/hooks/posttooluse-stale-result.ts +277 -0
  233. package/hooks/pretooluse-bash.ts +63 -0
  234. package/hooks/pretooluse-budget-guard.ts +318 -0
  235. package/hooks/pretooluse-common.ts +985 -0
  236. package/hooks/pretooluse-eco-router.ts +196 -0
  237. package/hooks/pretooluse-edit.sh +93 -0
  238. package/hooks/pretooluse-edit.ts +197 -0
  239. package/hooks/pretooluse-glob.ts +70 -0
  240. package/hooks/pretooluse-grep.sh +85 -0
  241. package/hooks/pretooluse-grep.ts +96 -0
  242. package/hooks/pretooluse-notebookedit.ts +121 -0
  243. package/hooks/pretooluse-read.sh +118 -0
  244. package/hooks/pretooluse-read.ts +118 -0
  245. package/hooks/pretooluse-task.ts +130 -0
  246. package/hooks/pretooluse-webfetch.ts +66 -0
  247. package/hooks/pretooluse-websearch.ts +85 -0
  248. package/hooks/pretooluse-write.ts +128 -0
  249. package/hooks/pulse-emit.ts +197 -0
  250. package/hooks/session-end-consolidate.sh +35 -0
  251. package/hooks/session-end-consolidate.ts +188 -0
  252. package/hooks/session-end-stats-gc.ts +80 -0
  253. package/hooks/session-log-append.sh +94 -0
  254. package/hooks/session-log-append.ts +116 -0
  255. package/hooks/session-start.ts +993 -0
  256. package/hooks/sessionend-hook-health-nudge.ts +451 -0
  257. package/hooks/sessionend-savings-nudge.ts +318 -0
  258. package/hooks/sessionend-status-line-nudge.ts +296 -0
  259. package/hooks/sessionstart-brief.ts +140 -0
  260. package/hooks/sessionstart-cost-refactor.ts +95 -0
  261. package/hooks/sessionstart-efficient.ts +110 -0
  262. package/hooks/sessionstart-genome-author.ts +93 -0
  263. package/hooks/sessionstart-lean-tools.ts +98 -0
  264. package/hooks/sessionstart-search.ts +97 -0
  265. package/hooks/stop-accounting.ts +152 -0
  266. package/hooks/subagent-stop-rollup.ts +162 -0
  267. package/hooks/userpromptsubmit-brief-trigger.ts +156 -0
  268. package/package.json +56 -0
  269. package/ports/README.md +108 -0
  270. package/ports/cursor/mcp.json +11 -0
  271. package/ports/goose/recipe.yaml +26 -0
  272. package/scripts/ashlr-mcp.ts +40 -0
  273. package/scripts/auto-update.ts +177 -0
  274. package/scripts/baseline-scan.ts +831 -0
  275. package/scripts/bench-orchestrate.ts +377 -0
  276. package/scripts/benchmark-refs.ts +292 -0
  277. package/scripts/bootstrap.mjs +100 -0
  278. package/scripts/brief-eval.ts +234 -0
  279. package/scripts/bump-version.ts +82 -0
  280. package/scripts/bun-resolve.d.mts +11 -0
  281. package/scripts/bun-resolve.mjs +59 -0
  282. package/scripts/calibrate-grep.ts +467 -0
  283. package/scripts/capture-grep-workload.ts +260 -0
  284. package/scripts/check-tier.ts +61 -0
  285. package/scripts/check-version-sync.ts +97 -0
  286. package/scripts/cli-orchestrate-status.ts +386 -0
  287. package/scripts/cli-orchestrate.ts +303 -0
  288. package/scripts/cli.ts +478 -0
  289. package/scripts/cloud-smoke-test.ts +313 -0
  290. package/scripts/coach-report.ts +373 -0
  291. package/scripts/context-db-migrate.ts +137 -0
  292. package/scripts/context-status.ts +43 -0
  293. package/scripts/demo-run.ts +255 -0
  294. package/scripts/deploy-smoke.sh +140 -0
  295. package/scripts/doctor.ts +992 -0
  296. package/scripts/embed-file-worker.ts +104 -0
  297. package/scripts/embed-tune.ts +308 -0
  298. package/scripts/errors-report.ts +428 -0
  299. package/scripts/find-test-leak.ts +69 -0
  300. package/scripts/generate-badge.ts +357 -0
  301. package/scripts/genome-auto-consolidate.ts +533 -0
  302. package/scripts/genome-auto-propose.ts +578 -0
  303. package/scripts/genome-cloud-pull.ts +363 -0
  304. package/scripts/genome-cloud-push.ts +382 -0
  305. package/scripts/genome-cloud-sync.ts +587 -0
  306. package/scripts/genome-commit-watcher.ts +295 -0
  307. package/scripts/genome-init.ts +986 -0
  308. package/scripts/genome-key.ts +233 -0
  309. package/scripts/genome-keygen.ts +168 -0
  310. package/scripts/genome-link.ts +59 -0
  311. package/scripts/genome-refresh-worker.ts +364 -0
  312. package/scripts/genome-rewrap.ts +90 -0
  313. package/scripts/genome-synthesizer.ts +679 -0
  314. package/scripts/genome-team-init.ts +312 -0
  315. package/scripts/handoff-pack.ts +438 -0
  316. package/scripts/hook-bootstrap.mjs +75 -0
  317. package/scripts/hook-timing-reader.ts +371 -0
  318. package/scripts/hook-timings-report.ts +554 -0
  319. package/scripts/install-genome-hooks.ts +196 -0
  320. package/scripts/install-onnx-model.ts +154 -0
  321. package/scripts/install-permissions.ts +276 -0
  322. package/scripts/install-status-line.ts +150 -0
  323. package/scripts/mcp-entrypoint.sh +78 -0
  324. package/scripts/mcp-entrypoint.ts +130 -0
  325. package/scripts/mdx-angle-bracket-lint.ts +152 -0
  326. package/scripts/measure-cold-start.ts +126 -0
  327. package/scripts/migrate-stats-to-sqlite.ts +86 -0
  328. package/scripts/ollama-setup.ts +498 -0
  329. package/scripts/onboarding-wizard.ts +1691 -0
  330. package/scripts/orchestrate-emit-telemetry.ts +177 -0
  331. package/scripts/orchestrate-expand.ts +517 -0
  332. package/scripts/orchestrate-run.ts +744 -0
  333. package/scripts/print-legend.ts +50 -0
  334. package/scripts/product-metadata.ts +38 -0
  335. package/scripts/prune-old-memories.ts +208 -0
  336. package/scripts/publish.sh +88 -0
  337. package/scripts/read-calibration.ts +122 -0
  338. package/scripts/report-crash.ts +226 -0
  339. package/scripts/run-benchmark.ts +750 -0
  340. package/scripts/run-root-tests.ts +28 -0
  341. package/scripts/savings-dashboard.ts +1258 -0
  342. package/scripts/savings-report-extras.ts +447 -0
  343. package/scripts/savings-status-line.ts +873 -0
  344. package/scripts/session-event-emit.ts +266 -0
  345. package/scripts/session-greet.ts +686 -0
  346. package/scripts/session-log-report.ts +551 -0
  347. package/scripts/session-resume.ts +614 -0
  348. package/scripts/set-hook-mode.ts +83 -0
  349. package/scripts/smoke-cross-platform.ts +117 -0
  350. package/scripts/smoke-realtime.ts +257 -0
  351. package/scripts/smoke-tools.ts +21 -0
  352. package/scripts/stats-cloud-pull.ts +213 -0
  353. package/scripts/stats-cloud-sync.ts +261 -0
  354. package/scripts/telemetry-flush.ts +160 -0
  355. package/scripts/telemetry-status.ts +266 -0
  356. package/scripts/timing-budget.ts +35 -0
  357. package/scripts/ui-animation.ts +120 -0
  358. package/scripts/ui.ts +431 -0
  359. package/scripts/upgrade-flow.ts +775 -0
  360. package/scripts/weekly-digest.ts +394 -0
  361. package/servers/_accounting.ts +78 -0
  362. package/servers/_ask-router.ts +84 -0
  363. package/servers/_ast-chunker.ts +434 -0
  364. package/servers/_ast-helpers.ts +183 -0
  365. package/servers/_ast-languages.ts +153 -0
  366. package/servers/_ast-refactor/_shared.ts +221 -0
  367. package/servers/_ast-refactor/cross-file-rename.ts +560 -0
  368. package/servers/_ast-refactor/extract-function.ts +357 -0
  369. package/servers/_ast-refactor/file-local-rename.ts +172 -0
  370. package/servers/_ast-refactor.ts +41 -0
  371. package/servers/_ast-skeleton.ts +107 -0
  372. package/servers/_bash-summarizers-registry.ts +1096 -0
  373. package/servers/_crash-dump.ts +210 -0
  374. package/servers/_cwd-clamp.ts +342 -0
  375. package/servers/_edit-log.ts +28 -0
  376. package/servers/_edit-match.ts +236 -0
  377. package/servers/_edit-validate.ts +196 -0
  378. package/servers/_embed-calibration.ts +123 -0
  379. package/servers/_embedding-cache.ts +401 -0
  380. package/servers/_embedding-model.ts +329 -0
  381. package/servers/_events.ts +113 -0
  382. package/servers/_genome-cache.ts +154 -0
  383. package/servers/_genome-commits.ts +172 -0
  384. package/servers/_genome-crypto-v2.ts +242 -0
  385. package/servers/_genome-crypto.ts +259 -0
  386. package/servers/_genome-discoveries.ts +140 -0
  387. package/servers/_genome-embed-populator.ts +216 -0
  388. package/servers/_genome-freshness.ts +134 -0
  389. package/servers/_genome-issues.ts +171 -0
  390. package/servers/_genome-live.ts +263 -0
  391. package/servers/_genome-prs.ts +188 -0
  392. package/servers/_genome-search.ts +479 -0
  393. package/servers/_genome-sync.ts +297 -0
  394. package/servers/_history-tracker.ts +279 -0
  395. package/servers/_http-helpers.ts +151 -0
  396. package/servers/_identity-hash.ts +151 -0
  397. package/servers/_llm-providers/anthropic.ts +132 -0
  398. package/servers/_llm-providers/cloud.ts +195 -0
  399. package/servers/_llm-providers/index.ts +350 -0
  400. package/servers/_llm-providers/local.ts +127 -0
  401. package/servers/_llm-providers/onnx.ts +393 -0
  402. package/servers/_llm-providers/types.ts +27 -0
  403. package/servers/_manifest-v2.ts +447 -0
  404. package/servers/_mcp-host.ts +93 -0
  405. package/servers/_nudge-events.ts +451 -0
  406. package/servers/_orchestrate-executor.ts +316 -0
  407. package/servers/_prefetch.ts +435 -0
  408. package/servers/_pricing.ts +256 -0
  409. package/servers/_pro.ts +381 -0
  410. package/servers/_read-cache.ts +44 -0
  411. package/servers/_router-handlers.ts +46 -0
  412. package/servers/_router.ts +55 -0
  413. package/servers/_run-with-timeout.ts +173 -0
  414. package/servers/_savings-render.ts +214 -0
  415. package/servers/_stats-sqlite.ts +640 -0
  416. package/servers/_stats.ts +1183 -0
  417. package/servers/_status-line-cells.ts +469 -0
  418. package/servers/_streaks.ts +162 -0
  419. package/servers/_summarize.ts +208 -0
  420. package/servers/_task-graph-render.ts +217 -0
  421. package/servers/_task-graph.ts +545 -0
  422. package/servers/_telemetry.ts +719 -0
  423. package/servers/_test-parsers.ts +530 -0
  424. package/servers/_test-watch.ts +626 -0
  425. package/servers/_text-helpers.ts +37 -0
  426. package/servers/_token-measure.ts +160 -0
  427. package/servers/_tool-base.ts +248 -0
  428. package/servers/_weekly-digest.ts +391 -0
  429. package/servers/ask-server-handlers.ts +49 -0
  430. package/servers/ask-server.ts +174 -0
  431. package/servers/bash-server-handlers.ts +138 -0
  432. package/servers/bash-server.ts +1136 -0
  433. package/servers/bench.ts +186 -0
  434. package/servers/diff-semantic-server-handlers.ts +39 -0
  435. package/servers/diff-semantic-server.ts +566 -0
  436. package/servers/diff-server-handlers.ts +317 -0
  437. package/servers/diff-server.ts +20 -0
  438. package/servers/edit-server-handlers.ts +31 -0
  439. package/servers/edit-server.ts +209 -0
  440. package/servers/edit-structural-server-handlers.ts +322 -0
  441. package/servers/efficiency-server-handlers.ts +161 -0
  442. package/servers/efficiency-server.ts +1135 -0
  443. package/servers/flush-server-handlers.ts +21 -0
  444. package/servers/flush-server.ts +19 -0
  445. package/servers/genome-server-handlers.ts +118 -0
  446. package/servers/genome-server.ts +397 -0
  447. package/servers/github-server-handlers.ts +187 -0
  448. package/servers/github-server.ts +856 -0
  449. package/servers/glob-server-handlers.ts +233 -0
  450. package/servers/glob-server.ts +20 -0
  451. package/servers/grep-server-handlers.ts +61 -0
  452. package/servers/grep-server.ts +564 -0
  453. package/servers/http-server-handlers.ts +36 -0
  454. package/servers/http-server.ts +149 -0
  455. package/servers/logs-server-handlers.ts +42 -0
  456. package/servers/logs-server.ts +359 -0
  457. package/servers/ls-server-handlers.ts +166 -0
  458. package/servers/ls-server.ts +20 -0
  459. package/servers/multi-edit-server-handlers.ts +51 -0
  460. package/servers/multi-edit-server.ts +422 -0
  461. package/servers/notebook-edit-server-handlers.ts +34 -0
  462. package/servers/notebook-edit-server.ts +181 -0
  463. package/servers/orient-server-handlers.ts +52 -0
  464. package/servers/orient-server.ts +436 -0
  465. package/servers/pipe-server-handlers.ts +68 -0
  466. package/servers/pipe-server.ts +300 -0
  467. package/servers/read-server-handlers.ts +42 -0
  468. package/servers/read-server.ts +206 -0
  469. package/servers/rename-file-server-handlers.ts +67 -0
  470. package/servers/rename-file-server.ts +699 -0
  471. package/servers/savings-server-handlers.ts +27 -0
  472. package/servers/savings-server.ts +54 -0
  473. package/servers/search-replace-regex-server-handlers.ts +96 -0
  474. package/servers/search-replace-regex-server.ts +751 -0
  475. package/servers/sql-server-handlers.ts +58 -0
  476. package/servers/sql-server.ts +505 -0
  477. package/servers/task-server-handlers.ts +358 -0
  478. package/servers/task-server.ts +24 -0
  479. package/servers/test-server-handlers.ts +351 -0
  480. package/servers/test-server.ts +20 -0
  481. package/servers/tree-server-handlers.ts +503 -0
  482. package/servers/tree-server.ts +20 -0
  483. package/servers/webfetch-server-handlers.ts +166 -0
  484. package/servers/webfetch-server.ts +20 -0
  485. package/servers/websearch-server-handlers.ts +215 -0
  486. package/servers/websearch-server.ts +19 -0
  487. package/servers/write-server-handlers.ts +27 -0
  488. package/servers/write-server.ts +97 -0
  489. package/skills/ashlr-brief/SKILL.md +80 -0
  490. package/skills/ashlr-codex-doctor/SKILL.md +21 -0
  491. package/skills/ashlr-codex-efficient-tools/SKILL.md +17 -0
  492. package/skills/ashlr-codex-genome-refresh/SKILL.md +20 -0
  493. package/skills/ashlr-codex-parallel-explore/SKILL.md +20 -0
  494. package/skills/ashlr-codex-savings/SKILL.md +17 -0
  495. package/skills/ashlr-cost-refactor/SKILL.md +50 -0
  496. package/skills/ashlr-efficient/SKILL.md +64 -0
  497. package/skills/ashlr-genome-author/SKILL.md +43 -0
  498. package/skills/ashlr-lean-tools/SKILL.md +43 -0
  499. 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
+ }