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.
Files changed (110) hide show
  1. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/PKG-INFO +1 -1
  2. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/__init__.py +1 -1
  3. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/_boot.py +23 -15
  4. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/_update.py +20 -6
  5. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/session.py +74 -16
  6. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli.egg-info/PKG-INFO +1 -1
  7. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/README.md +0 -0
  8. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/pyproject.toml +0 -0
  9. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/setup.cfg +0 -0
  10. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/README.md +0 -0
  11. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/__main__.py +0 -0
  12. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/_vendor/__init__.py +0 -0
  13. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/_vendor/_pro_test_derive.py +0 -0
  14. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/_wordmark.py +0 -0
  15. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/account.py +0 -0
  16. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/build_state.py +0 -0
  17. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/cli.py +0 -0
  18. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/codes.py +0 -0
  19. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/__init__.py +0 -0
  20. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/apply.py +0 -0
  21. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/auth.py +0 -0
  22. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/commit.py +0 -0
  23. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/explain.py +0 -0
  24. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/failing.py +0 -0
  25. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/fix.py +0 -0
  26. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/history.py +0 -0
  27. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/init_cmd.py +0 -0
  28. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/pr.py +0 -0
  29. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/replay.py +0 -0
  30. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/scaffold.py +0 -0
  31. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/show.py +0 -0
  32. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/undo.py +0 -0
  33. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/verify.py +0 -0
  34. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/commands/write.py +0 -0
  35. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/config.py +0 -0
  36. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/diffs.py +0 -0
  37. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/engine.py +0 -0
  38. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/gitio.py +0 -0
  39. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/langscan.py +0 -0
  40. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/product_ops.py +0 -0
  41. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/refusals.py +0 -0
  42. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/remote.py +0 -0
  43. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/render.py +0 -0
  44. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/result.py +0 -0
  45. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/run_store.py +0 -0
  46. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/runid.py +0 -0
  47. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli/style.py +0 -0
  48. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli.egg-info/SOURCES.txt +0 -0
  49. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli.egg-info/dependency_links.txt +0 -0
  50. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli.egg-info/entry_points.txt +0 -0
  51. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli.egg-info/requires.txt +0 -0
  52. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/substratum_cli.egg-info/top_level.txt +0 -0
  53. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_adaptive_prior.py +0 -0
  54. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_alias_warrant_trace.py +0 -0
  55. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_bidirectional_gate_contracts.py +0 -0
  56. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_coherence_evolution.py +0 -0
  57. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_coherences.py +0 -0
  58. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_connection_web_api.py +0 -0
  59. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_cross_entity_scoping.py +0 -0
  60. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_data.py +0 -0
  61. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_de28_negation_refuse.py +0 -0
  62. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_definite_description_handler.py +0 -0
  63. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_demo_regression_catalog.py +0 -0
  64. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_demo_response_shape.py +0 -0
  65. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_e1_e2_field_alignment.py +0 -0
  66. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_extraction_class_write_time.py +0 -0
  67. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_falsification_predictions.py +0 -0
  68. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_gap_d_wh_normalization.py +0 -0
  69. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_inference.py +0 -0
  70. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_inflection_cvc_rule.py +0 -0
  71. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_inflection_irregular.py +0 -0
  72. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_intervention.py +0 -0
  73. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_invariant.py +0 -0
  74. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_legal_citation_handler.py +0 -0
  75. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_lens_noop_seam.py +0 -0
  76. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_markup_tag_handler.py +0 -0
  77. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_nl_parser.py +0 -0
  78. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_nl_parser_adversarial.py +0 -0
  79. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_noise.py +0 -0
  80. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_pass2_contextual.py +0 -0
  81. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_perspectives.py +0 -0
  82. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_polysemous_token_alias_guard.py +0 -0
  83. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_possessive_normalization.py +0 -0
  84. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_predicate_canon.py +0 -0
  85. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_projection_gate.py +0 -0
  86. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_projection_gate_tag_question.py +0 -0
  87. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_query_normalizer.py +0 -0
  88. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_records_answer_format.py +0 -0
  89. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_referent_aboutness_gate.py +0 -0
  90. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_referent_quality_gate.py +0 -0
  91. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_refuse_message_topic_neutrality.py +0 -0
  92. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_relation_detection.py +0 -0
  93. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_relaxation.py +0 -0
  94. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_residual_content_gate.py +0 -0
  95. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_reverse_lookup_subject_filter.py +0 -0
  96. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_sec_statement_aliases.py +0 -0
  97. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_span_lens.py +0 -0
  98. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_span_lens_query_integration.py +0 -0
  99. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_substrate.py +0 -0
  100. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_synonymy_detector.py +0 -0
  101. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_table_ingest.py +0 -0
  102. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_triple_ingest.py +0 -0
  103. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_two_phase_schema.py +0 -0
  104. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_user_asserted_aliases.py +0 -0
  105. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_v2_api.py +0 -0
  106. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_v2_api_claims.py +0 -0
  107. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_v2_claim_determinism.py +0 -0
  108. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_word_role.py +0 -0
  109. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_word_role_wiring.py +0 -0
  110. {substratum_cli-0.2.0 → substratum_cli-0.2.2}/tests/test_xbrl_doc_aliases.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: substratum-cli
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Substratum: verified-or-refused code, with provenance. The terminal over the comprehension engine.
5
5
  Author: Substratum
6
6
  License: LicenseRef-Proprietary
@@ -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.0"
8
+ __version__ = "0.2.2"
@@ -13,9 +13,11 @@ from __future__ import annotations
13
13
 
14
14
  import hashlib
15
15
 
16
- # The PUBLIC substrate release name -- what the product shows, like a model version (GPT-5.2, Opus 4.8).
17
- # Decoupled from the internal mining tag (pattern_library_scaled_v15): that tag still drives the seal
18
- # hash (the seal must certify the ACTUAL build), but users see this. Bump only on a real public release.
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, libname, patterns, warm, standing, version,
57
- color=True, ascii_mode=False, logged_in=True, update_to="") -> list[str]:
58
- """Return the boot lines. `standing` is build_state.standing() (may be {})."""
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(libname, version)
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, libname = (str(s).replace("·", "-") for s in (name, census, langs, libname))
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
- lamp = (("*" if ascii_mode else "●") + " ready") if warm else (dot + " loading")
103
- lampc = c(GR, lamp) if warm else c(CH, lamp)
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" release {RELEASE} {lamp}",
118
- c(LAB, " release ") + c(DAT, RELEASE) + " " + lampc))
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 += [" " + c(GO, f"{arrow} update") + c(LAB, f" Substratum {update_to} is available")
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 += [" " + c(GO, "login") + c(LAB, " to ") + c(DAT, "fix") + c(LAB, " and ") + c(DAT, "write")
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 += [" " + c(GO, "help") + c(LAB, " see everything you can do")
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 available(current: str) -> str:
31
- """The newer published version if the cache knows of one, else "". Pure, instant, no network."""
30
+ def _cache() -> dict:
32
31
  try:
33
- latest = str(json.load(open(_cache_path(), encoding="utf-8")).get("latest") or "")
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
- if not latest:
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({"latest": latest, "t": int(time.time())}, fh)
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", libname, "", engine.is_warm(),
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 = (pn(S.g("lamp") + " ready", S.PROVEN) if engine.is_warm()
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(_boot.RELEASE, S.LABEL) + " " + state)
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
- if not self._history:
307
- self._say(" no commands yet this session", _DIM)
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
- self._say(self._paint(" session history", _BOLD))
310
- start = max(1, len(self._history) - 29)
311
- for i, c in enumerate(self._history[-30:], start):
312
- self._say(f" {self._paint(str(i).rjust(3), _DIM)} {c}")
313
- self._say(self._paint(" re-run: !<n> · !! · !<prefix>", _DIM))
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):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: substratum-cli
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Substratum: verified-or-refused code, with provenance. The terminal over the comprehension engine.
5
5
  Author: Substratum
6
6
  License: LicenseRef-Proprietary
File without changes
File without changes