alphaengine 0.6.0__tar.gz → 0.6.1__tar.gz

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 (70) hide show
  1. {alphaengine-0.6.0 → alphaengine-0.6.1}/PKG-INFO +1 -1
  2. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/_version.py +7 -1
  3. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/cli.py +158 -136
  4. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/demo.py +6 -2
  5. {alphaengine-0.6.0 → alphaengine-0.6.1}/.github/workflows/ci.yml +0 -0
  6. {alphaengine-0.6.0 → alphaengine-0.6.1}/.github/workflows/publish.yml +0 -0
  7. {alphaengine-0.6.0 → alphaengine-0.6.1}/.gitignore +0 -0
  8. {alphaengine-0.6.0 → alphaengine-0.6.1}/AGENTS.md +0 -0
  9. {alphaengine-0.6.0 → alphaengine-0.6.1}/LICENSE +0 -0
  10. {alphaengine-0.6.0 → alphaengine-0.6.1}/README.md +0 -0
  11. {alphaengine-0.6.0 → alphaengine-0.6.1}/SECURITY.md +0 -0
  12. {alphaengine-0.6.0 → alphaengine-0.6.1}/pyproject.toml +0 -0
  13. {alphaengine-0.6.0 → alphaengine-0.6.1}/scripts/gen_docs.py +0 -0
  14. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/__init__.py +0 -0
  15. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/__main__.py +0 -0
  16. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/agent/__init__.py +0 -0
  17. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/agent/answer.py +0 -0
  18. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/agent/driver.py +0 -0
  19. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/auth.py +0 -0
  20. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/book.py +0 -0
  21. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/client/__init__.py +0 -0
  22. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/client/agent.py +0 -0
  23. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/client/executor.py +0 -0
  24. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/client/session.py +0 -0
  25. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/commands.py +0 -0
  26. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/complete.py +0 -0
  27. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/connectors/__init__.py +0 -0
  28. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/__init__.py +0 -0
  29. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/backtest.py +0 -0
  30. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/factors.py +0 -0
  31. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/pairs.py +0 -0
  32. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/performance.py +0 -0
  33. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/profile.py +0 -0
  34. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/risk.py +0 -0
  35. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/screen.py +0 -0
  36. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/series_shapes.py +0 -0
  37. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/signals.py +0 -0
  38. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/stress.py +0 -0
  39. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/technical.py +0 -0
  40. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/validation.py +0 -0
  41. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/core/walkforward.py +0 -0
  42. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/demo_book.py +0 -0
  43. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/demo_returns.py +0 -0
  44. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/demo_signal.py +0 -0
  45. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/demo_universe.py +0 -0
  46. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/events.py +0 -0
  47. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/loaders.py +0 -0
  48. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/model.py +0 -0
  49. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/py.typed +0 -0
  50. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/repl.py +0 -0
  51. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/study/__init__.py +0 -0
  52. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/study/report.py +0 -0
  53. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/study/schema.py +0 -0
  54. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/sweep/__init__.py +0 -0
  55. {alphaengine-0.6.0 → alphaengine-0.6.1}/src/alphaengine/sweep/runner.py +0 -0
  56. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_agent.py +0 -0
  57. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_answer.py +0 -0
  58. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_book.py +0 -0
  59. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_cli.py +0 -0
  60. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_client.py +0 -0
  61. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_commands.py +0 -0
  62. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_events.py +0 -0
  63. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_executor_ops.py +0 -0
  64. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_goldens.py +0 -0
  65. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_loaders.py +0 -0
  66. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_model.py +0 -0
  67. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_screen.py +0 -0
  68. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_signals.py +0 -0
  69. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_smoke.py +0 -0
  70. {alphaengine-0.6.0 → alphaengine-0.6.1}/tests/test_sweep.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: alphaengine
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: Validated research tooling for investment strategies: deflation, overfitting detection, and honest trial counts.
5
5
  Project-URL: Homepage, https://github.com/quantOSC/alphaengine
6
6
  Project-URL: Documentation, https://github.com/quantOSC/alphaengine#readme
@@ -56,4 +56,10 @@ is what a changed figure costs. The API may still move underneath it.
56
56
  # A new published wheel is a new thing people `pip install`. While the leading
57
57
  # digit is 0 the MINOR position carries that, the same way 0.5.0 carried a
58
58
  # surface change that left every figure byte-identical.
59
- __version__ = "0.6.0"
59
+ #
60
+ # 0.6.1 IS THE SCREEN, NOT THE MATHS. No computed value moved. The session is a
61
+ # transcript now: a hue-cycling wait-star over a travelling plateau, cards for
62
+ # what a step found, a composer instead of a dashboard. Same rungs, same
63
+ # doors, same refusals. The published artefact is a new wheel because 0.6.0
64
+ # already left the building.
65
+ __version__ = "0.6.1"
@@ -190,10 +190,6 @@ def cyan(t: str) -> str:
190
190
  return _c("36", t)
191
191
 
192
192
 
193
- def magenta(t: str) -> str:
194
- return _c("35", t)
195
-
196
-
197
193
  def _rgb(r: int, g: int, b: int, text: str) -> str:
198
194
  """A single-character (or short) paint. Falls back to the 256-colour cube."""
199
195
  if not _tty() or not text:
@@ -212,16 +208,26 @@ def _mix(start: tuple[int, int, int], end: tuple[int, int, int], t: float, ch: s
212
208
  return _rgb(r, g, b, ch)
213
209
 
214
210
 
215
- def _gradient(
216
- text: str,
217
- start: tuple[int, int, int] = (40, 200, 190),
218
- end: tuple[int, int, int] = (140, 255, 170),
219
- ) -> str:
220
- """Paint a wordmark so it reads as a surface, not a logo font."""
221
- if not _tty() or not text:
222
- return text
223
- n = max(len(text) - 1, 1)
224
- return "".join(_mix(start, end, i / n, ch) for i, ch in enumerate(text))
211
+ def _accent(text: str) -> str:
212
+ """The one brand colour. Warm gold, the wait-star Claude Code taught people
213
+ to read as 'something is happening'."""
214
+ return _rgb(255, 176, 72, text)
215
+
216
+
217
+ _HUES: tuple[tuple[int, int, int], ...] = (
218
+ (255, 176, 72),
219
+ (255, 120, 80),
220
+ (220, 110, 190),
221
+ (130, 150, 255),
222
+ (64, 200, 210),
223
+ (90, 220, 140),
224
+ (210, 220, 80),
225
+ )
226
+
227
+
228
+ def _spin_mark(frame: int) -> str:
229
+ r, g, b = _HUES[frame % len(_HUES)]
230
+ return _rgb(r, g, b, STAR)
225
231
 
226
232
 
227
233
  # ── glyphs, which are not always available ─────────────────────────────────
@@ -262,16 +268,17 @@ ON = "●" if _UNICODE_OK else "*"
262
268
  OFF = "○" if _UNICODE_OK else "-"
263
269
  #: A failed step's marker.
264
270
  CROSS = "×" if _UNICODE_OK else "x"
265
- #: Drive narration rails. A tree, not a progress bar: each line is a step that
266
- #: has already run, and the past is not the graph's future.
267
- RAIL_V = "│" if _UNICODE_OK else "|"
268
- RAIL_T = "├" if _UNICODE_OK else "+"
269
- RAIL_L = "└" if _UNICODE_OK else "+"
270
- RAIL_H = "─" if _UNICODE_OK else "-"
271
- #: REPL prompt. The ASCII fallback is the original `>`.
272
- PROMPT = "▸" if _UNICODE_OK else ">"
273
- #: Agent thought marker.
274
- GLEAM = "✦" if _UNICODE_OK else "*"
271
+ #: Nested detail, Codex-style. A step that has already run, never what comes next.
272
+ NEST = "└" if _UNICODE_OK else "\\"
273
+ #: REPL composer. Claude Code uses `>`; we keep a heavier caret so a research
274
+ #: session does not look like a shell waiting for bash.
275
+ PROMPT = "❯" if _UNICODE_OK else ">"
276
+ #: The wait-mark Claude Code popularised: a star that changes colour while work
277
+ #: is blocked. ASCII `*` where the stream cannot take it.
278
+ STAR = "✻" if _UNICODE_OK else "*"
279
+ #: Wordmark. The product is a parameter surface, not a mascot; this is just a
280
+ #: pin so the welcome is one glance, not a painting.
281
+ PIN = "α" if _stream_handles("α") else "ae"
275
282
 
276
283
  #: The questions, as commands: what a quant TYPES maps to what the server
277
284
  #: RUNS. One table, read by the parser, the dispatcher and the shell alike.
@@ -364,29 +371,18 @@ def _pulse(elapsed: float, *, width: int = 14) -> str:
364
371
  """A travelling heartbeat. Deliberately NOT a progress bar: nothing here
365
372
  knows how long the step will take, and a bar that fills at a guessed rate
366
373
  is a lie told smoothly."""
367
- phase = (elapsed * 0.62) % 1.0
368
- values = [
369
- math.exp(-((((i / max(width - 1, 1)) - phase) * 4.8) ** 2))
370
- + 0.18 * math.exp(-((((i / max(width - 1, 1)) - ((phase + 0.55) % 1.0)) * 7.0) ** 2))
371
- for i in range(width)
372
- ]
373
- return sparkline(values, width=width)
374
-
374
+ return sparkline(_scan_wave(elapsed, width), width=width)
375
375
 
376
- def _chip(label: str, *, on: bool) -> str:
377
- """A HUD cell on the status line. Reverse-video when the stream can take it."""
378
- body = f" {label} "
379
- if not _tty():
380
- return f"[{label}]"
381
- if on:
382
- return _c("48;5;23;38;5;159", body)
383
- return _c("48;5;236;38;5;246", body)
384
376
 
385
-
386
- #: Spinner frames. Braille where the stream takes it, ASCII where it does not —
387
- #: the same degradation `DOT` and `ARROW` get, for the same reason: a Windows
388
- #: cp1252 console rendered the pretty version as mojibake.
389
- _SPIN = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏" if _UNICODE_OK else "|/-\\"
377
+ def _scan_wave(elapsed: float, width: int) -> list[float]:
378
+ """A plateau moving across the line. The peak wraps; duration is unknown."""
379
+ mu = (elapsed * 0.42) % 1.0
380
+ denom = max(width - 1, 1)
381
+ return [
382
+ math.exp(-0.5 * ((i / denom - mu) / 0.14) ** 2)
383
+ + 0.28 * math.exp(-0.5 * ((i / denom - ((mu + 0.52) % 1.0)) / 0.10) ** 2)
384
+ for i in range(width)
385
+ ]
390
386
 
391
387
 
392
388
  class Working:
@@ -442,18 +438,30 @@ class Working:
442
438
 
443
439
  def _animate(self) -> None:
444
440
  i = 0
445
- # A first frame immediately, then every 80ms. Waiting out the first
446
- # interval before drawing anything makes a fast step flash a blank line.
447
- # The heartbeat is a travelling pulse of elapsed time, not a bar that
448
- # claims to know when the work will finish.
441
+ # Two lines when the stream can take a ridge: a hue-cycling star (Claude
442
+ # Code) over a travelling plateau (the thing this tool actually judges).
443
+ # One line of ASCII otherwise. CSI erase is safe here because colour
444
+ # already committed us to ANSI the moment `_tty()` was true.
445
+ wide = _UNICODE_OK
449
446
  while True:
450
- pulse = _pulse(self.elapsed)
451
- text = f" {_SPIN[i % len(_SPIN)]} {self.label} {self.elapsed:.1f}s {cyan(pulse)}"
452
- plain_len = len(f" {_SPIN[0]} {self.plain} {self.elapsed:.1f}s {pulse}")
453
- sys.stdout.write("\r" + text)
447
+ mark = _spin_mark(i)
448
+ t = f"{self.elapsed:.1f}s"
449
+ if wide:
450
+ ridge = _spark_paint(_scan_wave(self.elapsed, 32), width=32)
451
+ block = f" {mark} {self.label}\n {ridge} {dim(t)}"
452
+ plain = 8 + max(len(self.plain), 32 + 2 + len(t))
453
+ else:
454
+ block = f" {_SPIN[i % len(_SPIN)]} {self.label} {t}"
455
+ plain = len(f" {_SPIN[0]} {self.plain} {t}")
456
+ if i == 0:
457
+ sys.stdout.write(block)
458
+ elif wide:
459
+ sys.stdout.write("\r\033[2K\033[1A\r\033[2K" + block)
460
+ else:
461
+ sys.stdout.write("\r\033[2K" + block)
454
462
  sys.stdout.flush()
455
- self._painted = max(self._painted, plain_len)
456
- if self._stop.wait(0.08):
463
+ self._painted = max(self._painted, plain)
464
+ if self._stop.wait(0.07):
457
465
  return
458
466
  i += 1
459
467
 
@@ -461,11 +469,10 @@ class Working:
461
469
  self._stop.set()
462
470
  if self._thread is not None:
463
471
  self._thread.join(timeout=0.5)
464
- # Erased with spaces rather than an ANSI erase-line: this runs on
465
- # consoles old enough that colour is the only escape sequence worth
466
- # assuming, and a stray `[2K` printed literally is worse than a
467
- # slightly wider blank.
468
- sys.stdout.write("\r" + " " * (self._painted + 2) + "\r")
472
+ if _UNICODE_OK:
473
+ sys.stdout.write("\r\033[2K\033[1A\r\033[2K")
474
+ else:
475
+ sys.stdout.write("\r" + " " * (self._painted + 2) + "\r")
469
476
  sys.stdout.flush()
470
477
 
471
478
 
@@ -1353,8 +1360,6 @@ def _drive(run: Any, *, quiet: bool = False) -> None:
1353
1360
  """
1354
1361
  n = 0
1355
1362
  failures: dict[str, int] = {}
1356
- if not quiet:
1357
- say(dim(f" {RAIL_V}"))
1358
1363
  while run.status == "open" and n < 200:
1359
1364
  if not run.permitted:
1360
1365
  run.resume()
@@ -1370,7 +1375,7 @@ def _drive(run: Any, *, quiet: bool = False) -> None:
1370
1375
  # `compute.*` executes in-process against the caller's own frames, so
1371
1376
  # this is the one place in the product where a long wait is entirely
1372
1377
  # local and entirely invisible.
1373
- with Working(f"{dim('running ' + ARROW)} {bold(op)}", plain=f"running {ARROW} {op}") as w:
1378
+ with Working(bold(op), plain=str(op)) as w:
1374
1379
  run.step(step)
1375
1380
  n += 1
1376
1381
  took = dim(f"{w.elapsed:.1f}s")
@@ -1384,11 +1389,12 @@ def _drive(run: Any, *, quiet: bool = False) -> None:
1384
1389
  # with no why sent people to the traceback that no longer
1385
1390
  # prints. The message came from the executor and was written
1386
1391
  # to be read.
1387
- say(f" {red(RAIL_T + RAIL_H)} {red('failed ' + CROSS)} {bold(op)} {took}")
1392
+ say(f" {red(CROSS)} {bold(op)}")
1393
+ say(f" {NEST} {took}")
1388
1394
  why = getattr(run, "last_error", None)
1389
1395
  if why:
1390
1396
  for line in str(why).splitlines():
1391
- say(red(f" {RAIL_V} {line}"))
1397
+ say(red(f" {NEST} {line}"))
1392
1398
  if failures[key] >= 2:
1393
1399
  run.status = "abandoned"
1394
1400
  run.stopped = {
@@ -1403,7 +1409,8 @@ def _drive(run: Any, *, quiet: bool = False) -> None:
1403
1409
  # `emit.*` seal rather than measure) and printing a blank line
1404
1410
  # for it would read as a stall.
1405
1411
  found = _found(seen, run)
1406
- say(f" {cyan(RAIL_T + RAIL_H)} {found or bold(op)} {took}")
1412
+ say(f" {_accent(ON)} {found or bold(op)}")
1413
+ say(f" {dim(NEST)} {took}")
1407
1414
 
1408
1415
  if run.status != "open":
1409
1416
  return
@@ -1501,7 +1508,7 @@ def _report(run: Any) -> int:
1501
1508
  """
1502
1509
  say("")
1503
1510
  if run.status == "closed":
1504
- say(green(f" {RAIL_L}{RAIL_H} Closed.") + " " + dim(str((run.artifact or {}).get("workflow", ""))))
1511
+ say(f" {_accent(ON)} {green('Closed')} " + dim(str((run.artifact or {}).get("workflow", ""))))
1505
1512
  _render_artifact(run.artifact or {})
1506
1513
  _say_gaps(run)
1507
1514
  _where_it_lives(run)
@@ -1509,8 +1516,8 @@ def _report(run: Any) -> int:
1509
1516
  return 0
1510
1517
  if run.status == "stopped":
1511
1518
  stop = run.stopped or {}
1512
- say(yellow(f" {RAIL_L}{RAIL_H} Stopped.") + " " + str(stop.get("reason", "")))
1513
- say(dim(" A stop is a result. The run did what it was built to do."))
1519
+ say(f" {yellow(ON)} {yellow('Stopped')} " + str(stop.get("reason", "")))
1520
+ say(dim(f" {NEST} A stop is a result. The run did what it was built to do."))
1514
1521
  # A STOP GETS ITS GAPS TOO, and needs them more than a close does: the
1515
1522
  # run refused to answer, so what it could not cover is most of what
1516
1523
  # there is to know about it.
@@ -1520,7 +1527,7 @@ def _report(run: Any) -> int:
1520
1527
  if run.status == "abandoned":
1521
1528
  stop = run.stopped or {}
1522
1529
  error = stop.get("error")
1523
- say(red(f" {RAIL_L}{RAIL_H} Abandoned.") + f" {stop.get('op')} failed twice, identically.")
1530
+ say(f" {red(CROSS)} {red('Abandoned')} {stop.get('op')} failed twice, identically.")
1524
1531
  if error:
1525
1532
  for line in str(error).splitlines():
1526
1533
  say(f" {line}")
@@ -1864,11 +1871,9 @@ def _render_artifact(art: dict[str, Any]) -> None:
1864
1871
  # separates from its value — "worst segment Sharpe-3.20" welded a
1865
1872
  # 20-character label straight into a negative number.
1866
1873
  width = max(20, max(len(label) for label, _, _ in shown))
1867
- peak = max(abs(float(v)) for _, v, _ in shown) or 1.0
1868
1874
  for label, value, unit in shown:
1869
1875
  tail = f" {dim(unit)}" if unit else ""
1870
- meter = _bar(float(value), peak)
1871
- say(f" {dim(label.ljust(width))} {bold(_num(value))}{tail} {meter}")
1876
+ say(f" {dim(label.ljust(width))} {bold(_num(value))}{tail}")
1872
1877
 
1873
1878
  # SAID EVEN THOUGH THE TABLE IS RIGHT THERE. A shortlist of 20 means one
1874
1879
  # thing when 480 names were measured and rejected, and quite another when
@@ -2319,7 +2324,7 @@ def _shape_phrase(data: Any) -> str | None:
2319
2324
 
2320
2325
 
2321
2326
  def status_line(url: str, *, data: Any, project: str | None, keyed: bool) -> str:
2322
- """One HUD line above the prompt: what is loaded, and what can run.
2327
+ """One quiet line above the prompt: what is loaded, and what can run.
2323
2328
 
2324
2329
  THE QUESTION THIS ANSWERS is the one asked most often inside a session and
2325
2330
  the one the boot screen answers once and then scrolls away: is my data in?
@@ -2328,28 +2333,26 @@ def status_line(url: str, *, data: Any, project: str | None, keyed: bool) -> str
2328
2333
  identical to one that did -- which is exactly how "the S&P 500 is a handful
2329
2334
  of names" happened.
2330
2335
 
2331
- Deliberately ONE LINE. It sits above every prompt, so anything taller
2332
- becomes wallpaper and stops being read at all. The cells are chips rather
2333
- than dim prose so the three facts (data, sign-in, model) can be scanned
2334
- without parsing separators.
2336
+ Deliberately ONE LINE and dim. Reverse-video chips became wallpaper; this
2337
+ is the Codex status strip: facts, not a second UI.
2335
2338
  """
2336
2339
  from .model import available_models
2337
2340
 
2338
- chips: list[str] = []
2341
+ bits: list[str] = []
2339
2342
 
2340
2343
  if data is None:
2341
- chips.append(_chip("no data", on=False))
2344
+ bits.append("no data")
2342
2345
  else:
2343
2346
  phrase = _shape_phrase(data)
2344
2347
  label = project or "loaded"
2345
- chips.append(_chip(label + (f" {DOT} {phrase}" if phrase else ""), on=True))
2348
+ bits.append(label + (f" {DOT} {phrase}" if phrase else ""))
2346
2349
 
2347
- chips.append(_chip("signed in" if keyed else "not signed in", on=keyed))
2350
+ bits.append("signed in" if keyed else "not signed in")
2348
2351
 
2349
2352
  models = available_models()
2350
- chips.append(_chip(models[0][0] if models else "no model", on=bool(models)))
2353
+ bits.append(models[0][0] if models else "no model")
2351
2354
 
2352
- return " " + " ".join(chips)
2355
+ return " " + dim(f" {DOT} ".join(bits))
2353
2356
 
2354
2357
 
2355
2358
  def cmd_commands(args: argparse.Namespace) -> int:
@@ -2377,7 +2380,7 @@ def cmd_commands(args: argparse.Namespace) -> int:
2377
2380
  rows = in_group(group)
2378
2381
  if not rows:
2379
2382
  continue
2380
- say(" " + cyan(bold(title)))
2383
+ say(" " + dim(title))
2381
2384
  width = max(len(_signature(c)) for c in rows)
2382
2385
  for c in rows:
2383
2386
  where = "" if c.scope == "both" else dim(f" ({c.scope})")
@@ -2388,7 +2391,7 @@ def cmd_commands(args: argparse.Namespace) -> int:
2388
2391
  # are the single thing people are most often hunting for when they open a
2389
2392
  # directory at all — "how do I give it my data" is not answerable from a
2390
2393
  # list of verbs.
2391
- say(" " + cyan(bold("Getting your data in")))
2394
+ say(" " + dim("Getting your data in"))
2392
2395
  width = max(len(d) for d, _ in DATA_DOORS)
2393
2396
  for door, what in DATA_DOORS:
2394
2397
  say(f" {door.ljust(width)} {dim(what)}")
@@ -2509,12 +2512,11 @@ def ladder_lines(*, keyed: bool) -> list[str]:
2509
2512
  ),
2510
2513
  ]
2511
2514
 
2512
- out = [" " + dim("Where you are")]
2513
- for live, name, does, note in rungs:
2515
+ out: list[str] = []
2516
+ for live, name, _does, note in rungs:
2514
2517
  mark = green(on) if live else dim(off)
2515
2518
  label = bold(name.ljust(14)) if live else dim(name.ljust(14))
2516
- does_s = does.ljust(44) if live else dim(does.ljust(44))
2517
- out.append(f" {mark} {label}{does_s}{note}")
2519
+ out.append(f" {mark} {label}{note}")
2518
2520
 
2519
2521
  # ONE ACTIONABLE LINE, and only when something is locked. The rungs
2520
2522
  # themselves stay in plain language — a menu that recites environment
@@ -2651,8 +2653,47 @@ def account_sleeve_lines(session: Any) -> list[str]:
2651
2653
  return out
2652
2654
 
2653
2655
 
2656
+ def _next_moves(*, keyed: bool, data: Any, project: str | None) -> list[str]:
2657
+ """The next three things to type. A boot screen that cannot name a first
2658
+ move is a logo."""
2659
+ from .model import available_models
2660
+
2661
+ models = available_models()
2662
+ rows: list[tuple[str, str]] = []
2663
+ if not keyed:
2664
+ rows = [
2665
+ ("demo", "the offline half, no account"),
2666
+ ("key quantos", "unlock workflows"),
2667
+ ("key anthropic", "then ask in your own words"),
2668
+ ]
2669
+ elif data is None:
2670
+ rows = [
2671
+ ("universe <name>", "stored closes from the portal"),
2672
+ ("--project research.momentum", "your module, your simulator"),
2673
+ ("demo", "see a grid survive, or not"),
2674
+ ]
2675
+ if not models:
2676
+ rows[2] = ("key anthropic", "ask in your own words")
2677
+ else:
2678
+ rows = [
2679
+ ("screen", "what is worth a look"),
2680
+ ("validate", "count the tries"),
2681
+ ]
2682
+ if models:
2683
+ rows.append(('"which names are overbought?"', "the model picks the path"))
2684
+ else:
2685
+ rows.append(("key anthropic", "ask in your own words"))
2686
+ if project:
2687
+ rows[1] = ("validate", "count the tries on this project")
2688
+ out = ["", " " + dim("Next")]
2689
+ width = max(len(cmd) for cmd, _ in rows)
2690
+ for cmd, why in rows:
2691
+ out.append(f" {bold(cmd.ljust(width))} {dim(why)}")
2692
+ return out
2693
+
2694
+
2654
2695
  def boot(url: str, *, project: str | None, data: Any, keyed: bool, session: Any = None) -> None:
2655
- """The opening screen. State first, decoration second.
2696
+ """The opening screen. A transcript header, not a dashboard.
2656
2697
 
2657
2698
  Suppressed entirely when stdout is not a TTY: a CI log wants a transcript.
2658
2699
  """
@@ -2662,57 +2703,40 @@ def boot(url: str, *, project: str | None, data: Any, keyed: bool, session: Any
2662
2703
  say(f"alphaengine {__version__} {DOT} {url}")
2663
2704
  return
2664
2705
 
2665
- # THE SURFACE IS THE PRODUCT. Two ridges, same width, opposite shapes: a
2666
- # plateau that survives a neighbour, and a knife that does not. Colour is
2667
- # height, not decoration — the bright part is the peak.
2668
- plateau = [math.exp(-0.5 * ((i / 35 - 0.5) / 0.18) ** 2) for i in range(36)]
2669
- edge = [math.exp(-0.5 * ((i / 35 - 0.5) / 0.028) ** 2) for i in range(36)]
2670
2706
  say("")
2671
- say(" " + _spark_paint(plateau, width=36, cool=True))
2672
- say(" " + cyan(PLATEAU) + " " + _gradient("alphaengine") + " " + dim(__version__))
2673
- say(" " + dim(EDGE) + " " + dim("Find out what survives."))
2674
- say(" " + _spark_paint(edge, width=36, cool=False))
2707
+ say(" " + _accent(PIN) + " " + bold("alphaengine") + " " + dim(__version__))
2708
+ say(" " + dim("Find out what survives."))
2675
2709
  say("")
2676
2710
 
2677
2711
  # Data is described by SHAPE, never printed. A boot screen that echoes the
2678
2712
  # first rows of somebody's price series into their scrollback — where it
2679
2713
  # will sit until the buffer rolls, and into any recording of the session —
2680
2714
  # is a boundary violation for the sake of a nicer banner.
2715
+ bits: list[str] = []
2681
2716
  if data is None:
2682
- loaded = dim("none") + dim(f" {DOT} pass --project to load yours")
2717
+ bits.append(dim("no data"))
2683
2718
  else:
2684
- phrase = _shape_phrase(data) # dict of series, DataFrame, or one sequence
2685
- loaded = green("ready") + (dim(f" {DOT} {phrase}") if phrase else "")
2686
-
2687
- # SIGNED IN IS A FACT THE USER SHOULD SEE, not infer from things working.
2688
- # It also disambiguates the two ways a key can be present: one survives a
2689
- # new terminal and one does not, and "why does it work here but not there"
2690
- # is exactly the confusion that costs an afternoon.
2691
- from .auth import load_stored
2719
+ phrase = _shape_phrase(data)
2720
+ label = project or "loaded"
2721
+ bits.append(green(label + (f" {DOT} {phrase}" if phrase else "")))
2722
+ bits.append(green("signed in") if keyed else dim("not signed in"))
2723
+ if keyed:
2724
+ from .auth import load_stored
2692
2725
 
2693
- stored = load_stored()
2694
- if not keyed:
2695
- signed = dim("no")
2696
- elif "QUANTOS_API_KEY" in stored and not _shell_set("QUANTOS_API_KEY", stored):
2697
- signed = green("yes") + dim(f" {DOT} stored on this machine")
2698
- else:
2699
- signed = green("yes") + dim(f" {DOT} from your shell")
2700
-
2701
- host = url.replace("https://", "").replace("http://", "")
2702
- rows: list[tuple[str, str]] = [
2703
- ("version", bold(__version__)),
2704
- ("host", dim(host)),
2705
- ("signed in", signed),
2706
- ("project", (bold(project) if project else dim("none"))),
2707
- ("data", loaded),
2708
- ]
2709
- for line in _boxed(rows, title="session"):
2710
- say(line)
2726
+ stored = load_stored()
2727
+ if "QUANTOS_API_KEY" in stored and not _shell_set("QUANTOS_API_KEY", stored):
2728
+ bits.append(dim("stored"))
2729
+ else:
2730
+ bits.append(dim("from your shell"))
2731
+ say(" " + f" {dim(DOT)} ".join(bits))
2711
2732
 
2712
2733
  say("")
2713
2734
  for line in ladder_lines(keyed=keyed):
2714
2735
  say(line)
2715
2736
 
2737
+ for line in _next_moves(keyed=keyed, data=data, project=project):
2738
+ say(line)
2739
+
2716
2740
  # ONLY WHEN IT CHANGES THE ANSWER: signed in, and nothing already loaded
2717
2741
  # from disk. A run that knows its data does not need a directory of the
2718
2742
  # account's, and the call is not worth making to print something ignorable.
@@ -2724,8 +2748,6 @@ def boot(url: str, *, project: str | None, data: Any, keyed: bool, session: Any
2724
2748
  say(line)
2725
2749
 
2726
2750
  say("")
2727
- # THE ONE LINE THAT IS NOT STATUS. It is here because it is the thing most
2728
- # often forgotten and the thing that makes the rest make sense.
2729
2751
  say(" " + dim("Your data stays on this machine. Only the findings ever travel."))
2730
2752
  say("")
2731
2753
 
@@ -2922,7 +2944,7 @@ def _ask(
2922
2944
  say(dim("The server offers no workflows, so there is nothing to choose from."))
2923
2945
  return None
2924
2946
 
2925
- say(f" {magenta(GLEAM)} {dim('agent ' + DOT)} {dim(label)}")
2947
+ say(f" {_accent(STAR)} {dim(label)}")
2926
2948
 
2927
2949
  # Choosing the workflow is the same shape as choosing a step: an index into
2928
2950
  # a list the server produced. Reusing `AgentDriver.pick` rather than writing
@@ -2930,7 +2952,7 @@ def _ask(
2930
2952
  driver = AgentDriver(
2931
2953
  think,
2932
2954
  goal=request,
2933
- on_thought=lambda why: say(f" {magenta(GLEAM)} {dim('agent ' + DOT)} {why}"),
2955
+ on_thought=lambda why: say(f" {_accent(STAR)} {_c('2;3', why)}"),
2934
2956
  sink=sink,
2935
2957
  provider=provider or None,
2936
2958
  model=model_name or None,
@@ -2998,7 +3020,7 @@ def _ask(
2998
3020
 
2999
3021
  repro = chosen.get("reproducible")
3000
3022
  say(
3001
- f" {magenta(GLEAM)} {dim('agent ' + DOT)} chose {bold(name)} "
3023
+ f" {_accent(ON)} chose {bold(name)} "
3002
3024
  + (green("reproducible") if repro else yellow("not reproducible"))
3003
3025
  )
3004
3026
 
@@ -3128,7 +3150,7 @@ def _answer(
3128
3150
 
3129
3151
  say("")
3130
3152
  for line in _wrap(answer.text):
3131
- say(f" {cyan(RAIL_V)} {line}")
3153
+ say(f" {dim(NEST)} {line}")
3132
3154
  # THE CAVEATS ARE NOT OPTIONAL AND ARE NOT STYLED AWAY. Each one is a claim
3133
3155
  # the figures cannot support without it, and every honesty control in this
3134
3156
  # product exists to survive exactly this last step to the screen.
@@ -3155,14 +3177,14 @@ def _wrap(text: str, width: int = 76) -> list[str]:
3155
3177
 
3156
3178
 
3157
3179
  def _repl_prompt(*, keyed: bool) -> str:
3158
- """The session caret. Colour says which rungs are live; the glyph is just a caret."""
3180
+ """The composer. Gold when the agent rung is live, otherwise quiet."""
3159
3181
  from .model import available_models
3160
3182
 
3161
3183
  mark = f"{PROMPT} "
3162
3184
  if keyed and available_models():
3163
- return cyan(bold(mark))
3185
+ return _accent(mark)
3164
3186
  if keyed:
3165
- return green(bold(mark))
3187
+ return green(mark)
3166
3188
  return bold(mark)
3167
3189
 
3168
3190
 
@@ -174,8 +174,12 @@ def run() -> int:
174
174
  if ui._tty():
175
175
  sharpes = [t.sharpe for t in result.trials if t.failed is None]
176
176
  ui.say("")
177
- ui.say(" " + ui._spark_paint(sharpes or [0.0], width=36, cool=True))
178
- ui.say(" " + ui.dim("trial Sharpes across the grid " + ui.DOT + " the ridge is the neighbourhood"))
177
+ ui.say(
178
+ f" {ui._accent(ui.ON)} {verdict.get('verdict')} "
179
+ + ui.dim(f"{result.n_trials} trials {ui.DOT} {surface.get('shape')}")
180
+ )
181
+ ui.say(" " + ui._spark_paint(sharpes or [0.0], width=32, cool=True))
182
+ ui.say(" " + ui.dim("trial Sharpes across the grid"))
179
183
  return 0
180
184
 
181
185
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes