alphaengine 0.9.3__tar.gz → 0.9.4__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.
- {alphaengine-0.9.3 → alphaengine-0.9.4}/PKG-INFO +1 -1
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/_version.py +5 -1
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/tui/app.py +136 -41
- {alphaengine-0.9.3 → alphaengine-0.9.4}/.github/workflows/ci.yml +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/.github/workflows/publish.yml +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/.gitignore +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/AGENTS.md +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/LICENSE +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/README.md +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/SECURITY.md +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/docs/assets/banner.png +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/docs/assets/data_boundary.png +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/docs/assets/session.png +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/docs/figure_contract.md +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/docs/workflows/evaluate_process.json +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/docs/workflows/research_week.json +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/docs/workflows/stress_study.json +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/pyproject.toml +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/scripts/gen_docs.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/__init__.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/__main__.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/agent/__init__.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/agent/answer.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/agent/driver.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/auth.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/book.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/charts.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/cli.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/client/__init__.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/client/agent.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/client/executor.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/client/session.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/commands.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/complete.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/connectors/__init__.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/__init__.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/active.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/allocate.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/backtest.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/covariance.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/cross_section.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/factors.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/pairs.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/panel.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/performance.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/process.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/profile.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/risk.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/screen.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/series_shapes.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/signals.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/stress.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/technical.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/trace.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/validation.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/core/walkforward.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/demo.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/demo_book.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/demo_returns.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/demo_signal.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/demo_universe.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/events.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/loaders.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/model.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/py.typed +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/repl.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/study/__init__.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/study/report.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/study/schema.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/sweep/__init__.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/sweep/runner.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/tui/__init__.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/src/alphaengine/tui/dispatch.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_agent.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_answer.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_book.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_cli.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_client.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_commands.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_events.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_executor_ops.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_goldens.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_loaders.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_math_audit.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_model.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_process.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_quant_maths.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_screen.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_signals.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_smoke.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_sweep.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_trace.py +0 -0
- {alphaengine-0.9.3 → alphaengine-0.9.4}/tests/test_tui.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: alphaengine
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.4
|
|
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
|
|
@@ -88,4 +88,8 @@ is what a changed figure costs. The API may still move underneath it.
|
|
|
88
88
|
# 0.9.3 IS THE SESSION, NOT THE MATHS. No computed value moved. The math pane
|
|
89
89
|
# is narrower. A sentence is shown the workflow it is choosing and what a step
|
|
90
90
|
# already measured, and a finished step is not run again.
|
|
91
|
-
|
|
91
|
+
#
|
|
92
|
+
# 0.9.4 IS THE SESSION, NOT THE MATHS. No computed value moved. The session
|
|
93
|
+
# opens on the desk: what is loaded, the thesis on its own lines, and the
|
|
94
|
+
# next thing to type.
|
|
95
|
+
__version__ = "0.9.4"
|
|
@@ -8,6 +8,7 @@ import re
|
|
|
8
8
|
import time
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
|
+
from rich.text import Text
|
|
11
12
|
from textual.app import App, ComposeResult
|
|
12
13
|
from textual.containers import Horizontal, Vertical
|
|
13
14
|
from textual.widgets import DataTable, Input, ListItem, ListView, RichLog, Sparkline, Static
|
|
@@ -17,23 +18,18 @@ from .dispatch import Desk, submit
|
|
|
17
18
|
|
|
18
19
|
_ANSI = re.compile(r"\x1b\[[0-9;]*m")
|
|
19
20
|
|
|
21
|
+
# Kept as whole lines so a short window still shows the desk above them.
|
|
20
22
|
_COMMANDS = (
|
|
21
23
|
"login",
|
|
22
24
|
"enter model key",
|
|
23
25
|
"load <name>",
|
|
24
26
|
"thesis",
|
|
25
|
-
"screen",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"validate",
|
|
29
|
-
"stress",
|
|
30
|
-
"overlap",
|
|
31
|
-
"size",
|
|
32
|
-
"monitor",
|
|
27
|
+
"screen diagnose signal",
|
|
28
|
+
"validate stress overlap",
|
|
29
|
+
"size monitor",
|
|
33
30
|
"run <name>",
|
|
34
31
|
"workflows",
|
|
35
|
-
"help",
|
|
36
|
-
"quit",
|
|
32
|
+
"help quit",
|
|
37
33
|
)
|
|
38
34
|
_URL_AFTER = {
|
|
39
35
|
"AZURE_OPENAI_API_KEY": "AZURE_OPENAI_ENDPOINT",
|
|
@@ -58,12 +54,13 @@ Screen { background: #0b0d10; }
|
|
|
58
54
|
color: #c5cdd6;
|
|
59
55
|
padding: 1 1;
|
|
60
56
|
overflow: auto;
|
|
57
|
+
border-right: solid #1c2128;
|
|
61
58
|
}
|
|
62
59
|
#transcript {
|
|
63
60
|
width: 1fr;
|
|
64
61
|
height: 1fr;
|
|
65
62
|
background: #0b0d10;
|
|
66
|
-
padding: 0
|
|
63
|
+
padding: 1 2 0 2;
|
|
67
64
|
}
|
|
68
65
|
#inspector {
|
|
69
66
|
width: 30;
|
|
@@ -84,9 +81,9 @@ Screen { background: #0b0d10; }
|
|
|
84
81
|
height: 1;
|
|
85
82
|
background: #0b0d10;
|
|
86
83
|
color: #6b7380;
|
|
87
|
-
padding: 0
|
|
84
|
+
padding: 0 2;
|
|
88
85
|
}
|
|
89
|
-
#prompt { dock: bottom; margin: 0 1; }
|
|
86
|
+
#prompt { dock: bottom; margin: 0 1 0 1; }
|
|
90
87
|
"""
|
|
91
88
|
|
|
92
89
|
|
|
@@ -101,6 +98,23 @@ def _fit(text: str, width: int = 22) -> str:
|
|
|
101
98
|
return text[: width - 1] + "…"
|
|
102
99
|
|
|
103
100
|
|
|
101
|
+
def _wrap(text: str, width: int) -> list[str]:
|
|
102
|
+
words = " ".join(str(text).split()).split(" ")
|
|
103
|
+
if not words or words == [""]:
|
|
104
|
+
return []
|
|
105
|
+
lines: list[str] = []
|
|
106
|
+
current = ""
|
|
107
|
+
for word in words:
|
|
108
|
+
if current and len(current) + 1 + len(word) > width:
|
|
109
|
+
lines.append(current)
|
|
110
|
+
current = word
|
|
111
|
+
else:
|
|
112
|
+
current = f"{current} {word}".strip()
|
|
113
|
+
if current:
|
|
114
|
+
lines.append(current)
|
|
115
|
+
return lines
|
|
116
|
+
|
|
117
|
+
|
|
104
118
|
class QuantOSApp(App[None]):
|
|
105
119
|
"""Conversation first. The math pane opens when a run has a line to show."""
|
|
106
120
|
|
|
@@ -139,6 +153,7 @@ class QuantOSApp(App[None]):
|
|
|
139
153
|
self.rail_text = ""
|
|
140
154
|
self.formula_text = ""
|
|
141
155
|
self.transcript: list[str] = []
|
|
156
|
+
self.turns = 0
|
|
142
157
|
|
|
143
158
|
def compose(self) -> ComposeResult:
|
|
144
159
|
yield Static("", id="status")
|
|
@@ -152,18 +167,12 @@ class QuantOSApp(App[None]):
|
|
|
152
167
|
yield Static("", id="inputs")
|
|
153
168
|
yield Sparkline([], id="spark")
|
|
154
169
|
yield DataTable(id="series", zebra_stripes=True)
|
|
155
|
-
yield Static("
|
|
170
|
+
yield Static("", id="hint")
|
|
156
171
|
yield Input(placeholder="Ask in plain English, or run <workflow>", id="prompt")
|
|
157
172
|
|
|
158
173
|
def on_mount(self) -> None:
|
|
159
174
|
self.query_one("#series", DataTable).cursor_type = "row"
|
|
160
175
|
self._refresh_rail()
|
|
161
|
-
log = self.query_one("#transcript", RichLog)
|
|
162
|
-
log.write("Ask in plain English.")
|
|
163
|
-
log.write("A sentence picks a workflow. run <name> follows it exactly.")
|
|
164
|
-
log.write("login signs you in. enter model key pastes whichever key you use.")
|
|
165
|
-
log.write("")
|
|
166
|
-
log.write("help lists what you can type. quit leaves.")
|
|
167
176
|
self.query_one(Input).focus()
|
|
168
177
|
self.set_interval(0.2, self._tick_working)
|
|
169
178
|
self._sync_account()
|
|
@@ -192,46 +201,58 @@ class QuantOSApp(App[None]):
|
|
|
192
201
|
else:
|
|
193
202
|
loaded = "nothing"
|
|
194
203
|
|
|
195
|
-
lines = [
|
|
196
|
-
|
|
204
|
+
lines = [
|
|
205
|
+
"desk",
|
|
197
206
|
f"{on} the maths",
|
|
198
|
-
" offline
|
|
199
|
-
"",
|
|
207
|
+
" offline",
|
|
200
208
|
f"{on if keyed else off} workflows",
|
|
201
209
|
" ready" if keyed else " sign in",
|
|
202
|
-
"",
|
|
203
210
|
f"{on if ask_on else off} ask",
|
|
204
211
|
f" {ask_note}",
|
|
205
212
|
"",
|
|
206
213
|
"loaded",
|
|
207
214
|
f" {loaded}",
|
|
208
|
-
"",
|
|
209
215
|
"thesis",
|
|
210
|
-
f" {_fit(thesis_label(self.desk.thesis) if self.desk.thesis else 'none')}",
|
|
211
|
-
"",
|
|
212
|
-
"universes",
|
|
213
216
|
]
|
|
217
|
+
if self.desk.thesis:
|
|
218
|
+
for piece in _wrap(thesis_label(self.desk.thesis), 22)[:3]:
|
|
219
|
+
lines.append(f" {piece}")
|
|
220
|
+
else:
|
|
221
|
+
lines.append(" none")
|
|
222
|
+
lines += ["", "universes"]
|
|
214
223
|
if self._universes:
|
|
215
224
|
for name in self._universes[:12]:
|
|
216
225
|
mark = " stored" if name in self._stored else ""
|
|
217
226
|
lines.append(f" {_fit(name, 20 - len(mark))}{mark}")
|
|
218
227
|
else:
|
|
219
228
|
lines.append(" none in reach")
|
|
229
|
+
lines += ["", "commands", *[f" {name}" for name in _COMMANDS]]
|
|
220
230
|
if not keyed:
|
|
221
231
|
lines += ["", "login", "signs you in"]
|
|
222
232
|
self.rail_text = "\n".join(lines)
|
|
223
233
|
self.query_one("#rail", Static).update(self.rail_text)
|
|
224
234
|
|
|
225
|
-
|
|
235
|
+
bar = Text()
|
|
236
|
+
bar.append("quantOS", style="bold #9ecbff")
|
|
237
|
+
bar.append(" ", style="")
|
|
238
|
+
bar.append("signed in" if keyed else "not signed in", style="#c5cdd6" if keyed else "#e6b450")
|
|
226
239
|
if self.desk.loaded:
|
|
227
|
-
|
|
240
|
+
bar.append(" ", style="")
|
|
241
|
+
bar.append(self.desk.loaded, style="#e6edf3")
|
|
228
242
|
elif self.desk.data is not None:
|
|
229
|
-
|
|
230
|
-
else:
|
|
231
|
-
bits.append("no data")
|
|
243
|
+
bar.append(" data loaded", style="#e6edf3")
|
|
232
244
|
if ask_on:
|
|
233
|
-
|
|
234
|
-
|
|
245
|
+
bar.append(" ", style="")
|
|
246
|
+
bar.append(models[0][0], style="#8b949e")
|
|
247
|
+
elif self.desk.thesis:
|
|
248
|
+
bar.append(" ", style="")
|
|
249
|
+
bar.append(_fit(thesis_label(self.desk.thesis), 28), style="#8b949e")
|
|
250
|
+
self.query_one("#status", Static).update(bar)
|
|
251
|
+
hint = Text(self._next_line(), style="#6b7380")
|
|
252
|
+
self.query_one("#hint", Static).update(hint)
|
|
253
|
+
prompt = self.query_one(Input)
|
|
254
|
+
if not (self.desk.pending_secret or self.desk.pending_provider or self.desk.pending_url):
|
|
255
|
+
prompt.placeholder = self._placeholder(ask_on)
|
|
235
256
|
|
|
236
257
|
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
237
258
|
text = event.value.strip()
|
|
@@ -247,6 +268,7 @@ class QuantOSApp(App[None]):
|
|
|
247
268
|
if self.desk.pending_url:
|
|
248
269
|
self._accept_url(text)
|
|
249
270
|
return
|
|
271
|
+
self.turns += 1
|
|
250
272
|
self.busy = True
|
|
251
273
|
self.activity = text
|
|
252
274
|
self._started = time.monotonic()
|
|
@@ -368,6 +390,76 @@ class QuantOSApp(App[None]):
|
|
|
368
390
|
label, model = ready[0]
|
|
369
391
|
log.write(f"Model ready: {label}/{model}. Ask in plain English.")
|
|
370
392
|
|
|
393
|
+
def _placeholder(self, ask_on: bool) -> str:
|
|
394
|
+
if self.desk.thesis and (ask_on or self.desk.think is not None):
|
|
395
|
+
return "Ask about this thesis"
|
|
396
|
+
if ask_on or self.desk.think is not None:
|
|
397
|
+
return "Ask in plain English, or run <workflow>"
|
|
398
|
+
if not self.desk.keyed:
|
|
399
|
+
return "login"
|
|
400
|
+
return "enter model key"
|
|
401
|
+
|
|
402
|
+
def _next_line(self) -> str:
|
|
403
|
+
if not self.desk.keyed:
|
|
404
|
+
return "login signs you in. enter model key pastes whichever key you use."
|
|
405
|
+
if self.desk.thesis:
|
|
406
|
+
return "Ask about this thesis. screen ranks the book. run <name> follows a workflow."
|
|
407
|
+
if self.desk.data is not None:
|
|
408
|
+
return "Ask about the loaded data. screen ranks it. run <name> follows a workflow."
|
|
409
|
+
return "Ask in plain English. A sentence picks a workflow. run <name> follows it exactly."
|
|
410
|
+
|
|
411
|
+
def _paint_desk(self, notes: list[str]) -> None:
|
|
412
|
+
"""The opening card. State first, then the one next move. Not a manual."""
|
|
413
|
+
from ..cli import thesis_text
|
|
414
|
+
|
|
415
|
+
log = self.query_one("#transcript", RichLog)
|
|
416
|
+
log.clear()
|
|
417
|
+
head = Text()
|
|
418
|
+
head.append("quantOS", style="bold #9ecbff")
|
|
419
|
+
head.append(" research session", style="#6b7380")
|
|
420
|
+
log.write(head)
|
|
421
|
+
log.write("")
|
|
422
|
+
|
|
423
|
+
state = Text()
|
|
424
|
+
state.append("signed in" if self.desk.keyed else "not signed in", style="#e6edf3")
|
|
425
|
+
if self.desk.loaded:
|
|
426
|
+
state.append(" ", style="")
|
|
427
|
+
state.append(self.desk.loaded, style="#9ecbff")
|
|
428
|
+
elif self.desk.data is not None:
|
|
429
|
+
state.append(" series loaded", style="#9ecbff")
|
|
430
|
+
log.write(state)
|
|
431
|
+
log.write("")
|
|
432
|
+
|
|
433
|
+
if self.desk.thesis:
|
|
434
|
+
log.write(Text("thesis", style="bold #8b949e"))
|
|
435
|
+
name = thesis_label(self.desk.thesis)
|
|
436
|
+
for piece in _wrap(name, 62):
|
|
437
|
+
log.write(Text(piece, style="#e6edf3"))
|
|
438
|
+
statement = thesis_text(self.desk.thesis)
|
|
439
|
+
if statement and statement != name:
|
|
440
|
+
log.write("")
|
|
441
|
+
for piece in _wrap(statement, 66)[:5]:
|
|
442
|
+
log.write(Text(piece, style="#8b949e"))
|
|
443
|
+
else:
|
|
444
|
+
log.write(Text("no thesis pinned", style="#6b7380"))
|
|
445
|
+
log.write(Text("thesis <name> chooses one, when the account has several.", style="#6b7380"))
|
|
446
|
+
|
|
447
|
+
shown = False
|
|
448
|
+
for note in notes:
|
|
449
|
+
plain = _plain(note).strip()
|
|
450
|
+
if not plain or plain.lower().startswith("thesis "):
|
|
451
|
+
continue
|
|
452
|
+
if not shown:
|
|
453
|
+
log.write("")
|
|
454
|
+
shown = True
|
|
455
|
+
for piece in _wrap(plain, 66):
|
|
456
|
+
log.write(Text(piece, style="#8b949e"))
|
|
457
|
+
|
|
458
|
+
log.write("")
|
|
459
|
+
log.write(Text("─" * 28, style="#1c2128"))
|
|
460
|
+
for piece in _wrap(self._next_line(), 66):
|
|
461
|
+
log.write(Text(piece, style="#c5cdd6"))
|
|
462
|
+
|
|
371
463
|
def _sync_account(self) -> None:
|
|
372
464
|
"""Refresh portal universes. One stored book loads itself."""
|
|
373
465
|
from ..cli import _narrow_to_universe, _stored_universes, narration_to
|
|
@@ -419,10 +511,13 @@ class QuantOSApp(App[None]):
|
|
|
419
511
|
named = ", ".join(thesis_label(r) for r in theses[:8])
|
|
420
512
|
notes.append(f"Theses on your account: {named}. Type thesis <name> to use one.")
|
|
421
513
|
log = self.query_one("#transcript", RichLog)
|
|
422
|
-
for note in notes
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
514
|
+
kept = [_plain(note).strip() for note in notes if _plain(note).strip()]
|
|
515
|
+
for plain in kept:
|
|
516
|
+
self.transcript.append(plain)
|
|
517
|
+
if self.turns == 0:
|
|
518
|
+
self._paint_desk(kept)
|
|
519
|
+
else:
|
|
520
|
+
for plain in kept:
|
|
426
521
|
log.write(plain)
|
|
427
522
|
self._refresh_rail()
|
|
428
523
|
|
|
@@ -482,7 +577,7 @@ class QuantOSApp(App[None]):
|
|
|
482
577
|
prompt.placeholder = f"paste {self.desk.pending_url}"
|
|
483
578
|
else:
|
|
484
579
|
prompt.password = False
|
|
485
|
-
|
|
580
|
+
self._refresh_rail()
|
|
486
581
|
self.busy = False
|
|
487
582
|
self.activity = ""
|
|
488
583
|
if self.desk.account_dirty:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|