substratum-cli 0.2.0__tar.gz → 0.2.2__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.
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/PKG-INFO +1 -1
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/__init__.py +1 -1
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/_boot.py +23 -15
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/_update.py +20 -6
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/session.py +74 -16
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli.egg-info/PKG-INFO +1 -1
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/README.md +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/pyproject.toml +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/setup.cfg +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/README.md +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/__main__.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/_vendor/__init__.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/_vendor/_pro_test_derive.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/_wordmark.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/account.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/build_state.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/cli.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/codes.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/__init__.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/apply.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/auth.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/commit.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/explain.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/failing.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/fix.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/history.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/init_cmd.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/pr.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/replay.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/scaffold.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/show.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/undo.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/verify.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/write.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/config.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/diffs.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/engine.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/gitio.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/langscan.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/product_ops.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/refusals.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/remote.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/render.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/result.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/run_store.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/runid.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/style.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli.egg-info/SOURCES.txt +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli.egg-info/dependency_links.txt +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli.egg-info/entry_points.txt +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli.egg-info/requires.txt +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli.egg-info/top_level.txt +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_adaptive_prior.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_alias_warrant_trace.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_bidirectional_gate_contracts.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_coherence_evolution.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_coherences.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_connection_web_api.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_cross_entity_scoping.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_data.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_de28_negation_refuse.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_definite_description_handler.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_demo_regression_catalog.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_demo_response_shape.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_e1_e2_field_alignment.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_extraction_class_write_time.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_falsification_predictions.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_gap_d_wh_normalization.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_inference.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_inflection_cvc_rule.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_inflection_irregular.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_intervention.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_invariant.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_legal_citation_handler.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_lens_noop_seam.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_markup_tag_handler.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_nl_parser.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_nl_parser_adversarial.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_noise.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_pass2_contextual.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_perspectives.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_polysemous_token_alias_guard.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_possessive_normalization.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_predicate_canon.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_projection_gate.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_projection_gate_tag_question.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_query_normalizer.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_records_answer_format.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_referent_aboutness_gate.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_referent_quality_gate.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_refuse_message_topic_neutrality.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_relation_detection.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_relaxation.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_residual_content_gate.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_reverse_lookup_subject_filter.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_sec_statement_aliases.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_span_lens.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_span_lens_query_integration.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_substrate.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_synonymy_detector.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_table_ingest.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_triple_ingest.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_two_phase_schema.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_user_asserted_aliases.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_v2_api.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_v2_api_claims.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_v2_claim_determinism.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_word_role.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_word_role_wiring.py +0 -0
- {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_xbrl_doc_aliases.py +0 -0
|
@@ -5,4 +5,4 @@ Import budget: this package is stdlib-only; the ONLY module reaching into backen
|
|
|
5
5
|
"""
|
|
6
6
|
# SINGLE SOURCE OF TRUTH for the release version. pyproject reads this (dynamic version), the boot shows
|
|
7
7
|
# it, and the update-check compares against it. To cut a release, bump ONLY this line, then publish.
|
|
8
|
-
__version__ = "0.2.
|
|
8
|
+
__version__ = "0.2.2"
|
|
@@ -13,9 +13,11 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import hashlib
|
|
15
15
|
|
|
16
|
-
# The PUBLIC
|
|
17
|
-
#
|
|
18
|
-
#
|
|
16
|
+
# The PUBLIC name of the first intelligence library -- a model version, like GPT-5.2 or Opus 4.8. Shown
|
|
17
|
+
# ONLY when this library actually backs the session: in AIRGAPPED-LOCAL mode (the engine + library shipped
|
|
18
|
+
# on-prem, present on this machine), or reported by the HOSTED engine once the full corpus is live on it.
|
|
19
|
+
# The boot NEVER shows this from a hardcoded local path -- the label must be a real representation of the
|
|
20
|
+
# intelligence serving the user. The internal mining tag (pattern_library_scaled_v15) stays internal.
|
|
19
21
|
RELEASE = "Substrate 1.0 Preview"
|
|
20
22
|
|
|
21
23
|
L = "\033[38;5;154m" # brand lime
|
|
@@ -53,18 +55,21 @@ def seal(lib_name: str, version: str) -> tuple[str, str]:
|
|
|
53
55
|
return sigil, digest
|
|
54
56
|
|
|
55
57
|
|
|
56
|
-
def render(name, census, langs,
|
|
57
|
-
color=True, ascii_mode=False, logged_in=True, update_to="") -> list[str]:
|
|
58
|
-
"""Return the boot lines. `
|
|
58
|
+
def render(name, census, langs, substrate, substrate_note, substrate_ok, standing, version,
|
|
59
|
+
color=True, ascii_mode=False, logged_in=True, update_to="", seal_key="") -> list[str]:
|
|
60
|
+
"""Return the boot lines. `substrate` is the HONEST name of the intelligence serving this session
|
|
61
|
+
(the hosted engine version, or 'not connected') -- NEVER a local path. `substrate_note`/`_ok` render
|
|
62
|
+
the connection state. `seal_key` is the stable identity the self-certificate hashes."""
|
|
59
63
|
def c(code, s):
|
|
60
64
|
return f"{code}{s}{R}" if color else s
|
|
61
65
|
|
|
62
|
-
sig, dig = seal(
|
|
66
|
+
sig, dig = seal(seal_key or substrate, version)
|
|
63
67
|
if ascii_mode:
|
|
64
68
|
sig = "#" + dig.split()[0]
|
|
65
69
|
dot = "-" if ascii_mode else "·" # separators degrade with the glyph set
|
|
66
70
|
if ascii_mode: # the renderer guarantees ascii output regardless of input
|
|
67
|
-
name, census, langs,
|
|
71
|
+
name, census, langs, substrate, substrate_note = (
|
|
72
|
+
str(s).replace("·", "-") for s in (name, census, langs, substrate, substrate_note))
|
|
68
73
|
W = 78 # certificate inner width
|
|
69
74
|
out = ["", ""]
|
|
70
75
|
|
|
@@ -99,8 +104,9 @@ def render(name, census, langs, libname, patterns, warm, standing, version,
|
|
|
99
104
|
pad = " " * max(0, W - 1 - _vis(inner_plain))
|
|
100
105
|
return c(CH, v_) + " " + inner_colored + pad + c(CH, v_)
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
glyph = ("*" if ascii_mode else "●") if substrate_ok else dot
|
|
108
|
+
lamp = f"{glyph} {substrate_note}"
|
|
109
|
+
lampc = c(GR if substrate_ok else CH, lamp)
|
|
104
110
|
st = standing or {}
|
|
105
111
|
runs, ver = st.get("runs", 0), st.get("verified", 0)
|
|
106
112
|
unsaved = st.get("unapplied_total", 0)
|
|
@@ -114,8 +120,8 @@ def render(name, census, langs, libname, patterns, warm, standing, version,
|
|
|
114
120
|
box.append(c(CH, v_) + " " + seal_inner_c + pad + right_c + c(CH, v_))
|
|
115
121
|
|
|
116
122
|
box.append(bar(label="SUBSTRATE"))
|
|
117
|
-
box.append(rowline(f"
|
|
118
|
-
c(LAB, "
|
|
123
|
+
box.append(rowline(f" engine {substrate} {lamp}",
|
|
124
|
+
c(LAB, " engine ") + c(DAT, substrate) + " " + lampc))
|
|
119
125
|
box.append(rowline(f" covers {langs}", c(LAB, " covers ") + c(DAT, langs)))
|
|
120
126
|
box.append(bar(label="REPO"))
|
|
121
127
|
box.append(rowline(f" project {name}", c(LAB, " project ") + c(DAT, name)))
|
|
@@ -145,16 +151,18 @@ def render(name, census, langs, libname, patterns, warm, standing, version,
|
|
|
145
151
|
# fix + write run on the hosted engine; summarize/failing/verify run offline. Say so BEFORE they hit
|
|
146
152
|
# a NO_ENGINE refusal, and pick an ask-example that actually works in their current state.
|
|
147
153
|
ask_ex = '"fix the failing test"' if logged_in else '"what does this codebase do?"'
|
|
154
|
+
# the signpost text aligns at col 4 -- the same column as the welcome-back text -- so the whole bottom
|
|
155
|
+
# "here is what you can do" region reads as one clean left edge instead of jutting out under the box.
|
|
148
156
|
out += [""]
|
|
149
157
|
if update_to: # a pip package cannot self-update; hand over the one command
|
|
150
158
|
arrow = "^" if ascii_mode else "↑"
|
|
151
|
-
out += ["
|
|
159
|
+
out += [" " + c(GO, f"{arrow} update") + c(LAB, f" Substratum {update_to} is available")
|
|
152
160
|
+ c(CH, f" {dot} ") + c(GO, "pip install --upgrade substratum-cli")]
|
|
153
161
|
if not logged_in:
|
|
154
|
-
out += ["
|
|
162
|
+
out += [" " + c(GO, "login") + c(LAB, " to ") + c(DAT, "fix") + c(LAB, " and ") + c(DAT, "write")
|
|
155
163
|
+ c(LAB, " code (connects the engine)")
|
|
156
164
|
+ c(CH, f" {dot} ") + c(LAB, "summarize, failing, verify work offline")]
|
|
157
|
-
out += ["
|
|
165
|
+
out += [" " + c(GO, "help") + c(LAB, " see everything you can do")
|
|
158
166
|
+ c(CH, f" {dot} ") + c(LAB, "or just ask: ") + c(GO, ask_ex)]
|
|
159
167
|
return out
|
|
160
168
|
|
|
@@ -27,15 +27,25 @@ def _version_tuple(s: str) -> tuple:
|
|
|
27
27
|
return tuple(out)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
def
|
|
31
|
-
"""The newer published version if the cache knows of one, else "". Pure, instant, no network."""
|
|
30
|
+
def _cache() -> dict:
|
|
32
31
|
try:
|
|
33
|
-
|
|
32
|
+
return json.load(open(_cache_path(), encoding="utf-8")) or {}
|
|
34
33
|
except Exception:
|
|
35
|
-
return
|
|
34
|
+
return {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def available(current: str) -> str:
|
|
38
|
+
"""The newer published version if the cache knows of one, else "". Pure, instant, no network."""
|
|
39
|
+
latest = str(_cache().get("latest") or "")
|
|
36
40
|
return latest if latest and _version_tuple(latest) > _version_tuple(current) else ""
|
|
37
41
|
|
|
38
42
|
|
|
43
|
+
def engine_version() -> str:
|
|
44
|
+
"""The HOSTED engine's substrate version from the last check (cached), or "". This is what actually
|
|
45
|
+
serves fix/write for a thin client, so the boot shows THIS -- never a local path."""
|
|
46
|
+
return str(_cache().get("engine") or "")
|
|
47
|
+
|
|
48
|
+
|
|
39
49
|
def refresh_async() -> None:
|
|
40
50
|
"""Fire-and-forget: ask the server for the latest published client and cache it for next session.
|
|
41
51
|
The server sources this from PyPI, so publishing is all it takes -- no redeploy, no constant to bump."""
|
|
@@ -44,12 +54,16 @@ def refresh_async() -> None:
|
|
|
44
54
|
from . import remote
|
|
45
55
|
info = remote.server_info() or {}
|
|
46
56
|
latest = str(info.get("latest_client") or "")
|
|
47
|
-
|
|
57
|
+
engine = str(info.get("engine_version") or "")
|
|
58
|
+
if not (latest or engine):
|
|
48
59
|
return
|
|
49
60
|
path = _cache_path()
|
|
50
61
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
62
|
+
cur = _cache()
|
|
63
|
+
cur.update({"latest": latest or cur.get("latest", ""),
|
|
64
|
+
"engine": engine or cur.get("engine", ""), "t": int(time.time())})
|
|
51
65
|
with open(path, "w", encoding="utf-8") as fh:
|
|
52
|
-
json.dump(
|
|
66
|
+
json.dump(cur, fh)
|
|
53
67
|
except Exception:
|
|
54
68
|
pass
|
|
55
69
|
try:
|
|
@@ -12,6 +12,7 @@ model, the run store, and the gate are all identical. The session adds no new en
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
import os
|
|
15
|
+
import re
|
|
15
16
|
import shlex
|
|
16
17
|
import sys
|
|
17
18
|
import threading
|
|
@@ -107,33 +108,50 @@ class Session:
|
|
|
107
108
|
pn = S.Painter(self.color)
|
|
108
109
|
name = os.path.basename(self.repo) or self.repo
|
|
109
110
|
langs, files = self._repo_summary()
|
|
110
|
-
lib = engine.lib_roots.py_library_root()
|
|
111
|
-
libname = os.path.basename(lib) if lib else "none"
|
|
112
111
|
census = f"{files} files" + ((" · " + langs.replace(" ", " · ")) if langs else "")
|
|
113
112
|
st = build_state.standing(self.repo)
|
|
114
113
|
ascii_mode = S.use_ascii()
|
|
115
114
|
logged_in = account.is_logged_in()
|
|
116
115
|
_update.refresh_async() # refresh the update cache in the background (next session)
|
|
117
116
|
update_to = _update.available(_cver) # instant read from cache; "" if none or unknown
|
|
117
|
+
|
|
118
|
+
# HONEST substrate: which intelligence actually serves fix/write this session, and in which mode.
|
|
119
|
+
# Two real deployment modes: HOSTED (thin client -> engine on Fly) and AIRGAPPED-LOCAL (engine +
|
|
120
|
+
# library shipped on-prem). Never claim a substrate that is not backing this session.
|
|
121
|
+
local_present = False
|
|
122
|
+
try:
|
|
123
|
+
_p = engine.lib_roots.py_library_root()
|
|
124
|
+
local_present = bool(_p) and os.path.exists(_p)
|
|
125
|
+
except Exception:
|
|
126
|
+
local_present = False # a thin client has no local library -- that is fine
|
|
127
|
+
ev = _update.engine_version() # hosted engine's reported version (cached), "" if unknown
|
|
128
|
+
if logged_in: # hosted mode: write/fix reach the engine on the server
|
|
129
|
+
substrate = ev or "hosted engine"
|
|
130
|
+
sub_note, sub_ok, seal_key = (("hosted · connected", True, ev) if ev else ("connecting", False, "hosted"))
|
|
131
|
+
elif local_present: # airgapped / self-hosted: the on-disk library serves it
|
|
132
|
+
_warm = engine.is_warm()
|
|
133
|
+
substrate = _boot.RELEASE
|
|
134
|
+
sub_note, sub_ok, seal_key = (("local · ready" if _warm else "local · loading"), _warm, _boot.RELEASE)
|
|
135
|
+
else: # thin client, not connected -> claim nothing
|
|
136
|
+
substrate, sub_note, sub_ok, seal_key = "not connected", "login to connect", False, "offline"
|
|
118
137
|
# THE PROOF SEAL -- a self-certifying certificate boot: a byte-identical determinism seal (a real
|
|
119
138
|
# hash of engine + substrate + scheme, no time/session input), the struck-plane emblem (the logo),
|
|
120
139
|
# and a structured ledger. The sameness of the seal every boot IS the proof of determinism -- a
|
|
121
140
|
# token-streaming tool cannot draw a stable self-certificate. Needs a wide pane; compact below.
|
|
122
141
|
if S.seal_cols() >= 84:
|
|
123
|
-
for ln in _boot.render(name, census, "py · go · ts",
|
|
142
|
+
for ln in _boot.render(name, census, "py · go · ts", substrate, sub_note, sub_ok,
|
|
124
143
|
st, "0.1.0", color=self.color, ascii_mode=ascii_mode,
|
|
125
|
-
logged_in=logged_in, update_to=update_to):
|
|
144
|
+
logged_in=logged_in, update_to=update_to, seal_key=seal_key):
|
|
126
145
|
self._say(ln)
|
|
127
146
|
return
|
|
128
147
|
# compact identity (narrow pane): the mark + a lime-railed status
|
|
129
|
-
state =
|
|
130
|
-
else pn(S.g("dot") + " loading", S.CHROME))
|
|
148
|
+
state = pn(sub_note, S.PROVEN if sub_ok else S.CHROME)
|
|
131
149
|
rail = ("\033[38;5;154m▌\033[0m" if (self.color and not ascii_mode) else "|")
|
|
132
150
|
brand = (f"\033[38;5;154m{S.g('brand')}\033[0m" if (self.color and not ascii_mode)
|
|
133
151
|
else pn(S.g("brand"), S.PROVEN))
|
|
134
152
|
self._say(brand + " " + pn("SUBSTRATUM", S.BOLD) + pn(" proof-carrying · deterministic", S.LABEL))
|
|
135
153
|
self._say(f" {rail} " + pn(name, S.BOLD) + " " + pn(census, S.LABEL))
|
|
136
|
-
self._say(f" {rail} " + pn(
|
|
154
|
+
self._say(f" {rail} " + pn(substrate, S.LABEL) + " " + state)
|
|
137
155
|
if st:
|
|
138
156
|
bits = pn(f"{st['runs']} runs", S.LABEL)
|
|
139
157
|
if st.get("verified"):
|
|
@@ -179,6 +197,15 @@ class Session:
|
|
|
179
197
|
return None, True
|
|
180
198
|
self._say(" " + self._paint(recalled, _DIM)) # echo what is being re-run
|
|
181
199
|
line = recalled
|
|
200
|
+
# plain-language re-run: "run 12" replays history line 12 (clearer than the cryptic "!" syntax)
|
|
201
|
+
_rm = re.match(r"^run\s+(-?\d+)$", line)
|
|
202
|
+
if _rm:
|
|
203
|
+
recalled = self._recall(_rm.group(1))
|
|
204
|
+
if recalled is None:
|
|
205
|
+
self._say(f" no history line {_rm.group(1)} -- type history to see them", _CYAN)
|
|
206
|
+
return None, True
|
|
207
|
+
self._say(" " + self._paint(recalled, _DIM))
|
|
208
|
+
line = recalled
|
|
182
209
|
try:
|
|
183
210
|
parts = shlex.split(line)
|
|
184
211
|
except ValueError:
|
|
@@ -254,7 +281,7 @@ class Session:
|
|
|
254
281
|
{"login": auth.cmd_login, "logout": auth.cmd_logout, "whoami": auth.cmd_whoami}[cmd]()
|
|
255
282
|
return None, True
|
|
256
283
|
if cmd in ("history", "hist"):
|
|
257
|
-
self._history_cmd()
|
|
284
|
+
self._history_cmd(" ".join(args))
|
|
258
285
|
return None, True
|
|
259
286
|
if cmd in ("runs", "log"):
|
|
260
287
|
from .commands.history import cmd_runs
|
|
@@ -302,15 +329,46 @@ class Session:
|
|
|
302
329
|
return c
|
|
303
330
|
return None
|
|
304
331
|
|
|
305
|
-
def _history_cmd(self):
|
|
306
|
-
|
|
307
|
-
|
|
332
|
+
def _history_cmd(self, arg=""):
|
|
333
|
+
# The trail is PERSISTENT (grows across sessions, can be hundreds), so never dump it all: show a
|
|
334
|
+
# recent window with the total, let `history <word>` search and `history all` widen. Re-run is a
|
|
335
|
+
# plain verb -- `run <n>` -- not the cryptic bash `!` (which still works for shell muscle memory).
|
|
336
|
+
from . import style as S
|
|
337
|
+
pn = S.Painter(self.color)
|
|
338
|
+
h = self._history
|
|
339
|
+
if not h:
|
|
340
|
+
self._say(" " + pn("no commands yet", S.CHROME))
|
|
341
|
+
return
|
|
342
|
+
arg = (arg or "").strip()
|
|
343
|
+
total = len(h)
|
|
344
|
+
indexed = list(enumerate(h, 1))
|
|
345
|
+
if arg == "all":
|
|
346
|
+
items = indexed[-60:]
|
|
347
|
+
head = f"showing last {len(items)} of {total}"
|
|
348
|
+
elif arg:
|
|
349
|
+
hits = [(i, c) for i, c in indexed if arg.lower() in c.lower()]
|
|
350
|
+
items = hits[-40:]
|
|
351
|
+
head = (f"{len(hits)} match" + ("" if len(hits) == 1 else "es") + f' for "{arg}"'
|
|
352
|
+
+ (" (last 40)" if len(hits) > 40 else ""))
|
|
353
|
+
else:
|
|
354
|
+
items = indexed[-15:]
|
|
355
|
+
head = (f"last {len(items)} of {total}" if total > len(items)
|
|
356
|
+
else f"{total} command" + ("" if total == 1 else "s"))
|
|
357
|
+
self._say(" " + pn("history", S.BOLD) + " " + pn(head, S.LABEL))
|
|
358
|
+
w = len(str(total))
|
|
359
|
+
for i, c in items:
|
|
360
|
+
self._say(" " + pn(str(i).rjust(w), S.CHROME) + " "
|
|
361
|
+
+ pn(S.ellipsize(c, S.read_width() - w - 8), S.LABEL))
|
|
362
|
+
if not items:
|
|
363
|
+
self._say(" " + pn(f'nothing matches "{arg}" -- try another word', S.CHROME))
|
|
308
364
|
return
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
365
|
+
eg = items[-1][0] # a concrete number the user can copy
|
|
366
|
+
bits = [pn("run " + str(eg), S.GOLD) + pn(" replays that line", S.CHROME)]
|
|
367
|
+
if not arg:
|
|
368
|
+
bits.append(pn("history <word>", S.GOLD) + pn(" searches", S.CHROME))
|
|
369
|
+
if total > len(items):
|
|
370
|
+
bits.append(pn("history all", S.GOLD) + pn(" shows more", S.CHROME))
|
|
371
|
+
self._say(" " + pn(" · ", S.CHROME).join(bits))
|
|
314
372
|
|
|
315
373
|
# ---- natural-language router (closed-class, deterministic, no LLM) ----
|
|
316
374
|
def _route_nl(self, line: str):
|
|
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
|
|
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
|