axiometa-cli 1.0.2__tar.gz → 1.0.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.
Files changed (24) hide show
  1. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/PKG-INFO +1 -1
  2. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/__init__.py +9 -1
  3. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/core.py +30 -4
  4. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/flash.py +11 -5
  5. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/interactive.py +15 -10
  6. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli.egg-info/PKG-INFO +1 -1
  7. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/pyproject.toml +1 -1
  8. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/README.md +0 -0
  9. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/__main__.py +0 -0
  10. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/auth.py +0 -0
  11. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/chat.py +0 -0
  12. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/cli.py +0 -0
  13. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/detect.py +0 -0
  14. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/knowledge_client.py +0 -0
  15. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/provision.py +0 -0
  16. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/serve.py +0 -0
  17. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/setup.py +0 -0
  18. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli/ui.py +0 -0
  19. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli.egg-info/SOURCES.txt +0 -0
  20. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli.egg-info/dependency_links.txt +0 -0
  21. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli.egg-info/entry_points.txt +0 -0
  22. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli.egg-info/requires.txt +0 -0
  23. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/axiometa_cli.egg-info/top_level.txt +0 -0
  24. {axiometa_cli-1.0.2 → axiometa_cli-1.0.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axiometa-cli
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: Prompt to hardware, in your terminal: detect a board, chat to build, deploy, watch serial.
5
5
  Author-email: Axiometa <dr.dumcius@gmail.com>
6
6
  Project-URL: Homepage, https://axiometa.io
@@ -16,4 +16,12 @@ Three ideas the CLI implements, all grounded in the backend registries:
16
16
  - The serial CHANNEL is one contract; only its TRANSPORT changes: raw stdout in a terminal, or a
17
17
  127.0.0.1 + token-gated localhost page (opened locally, or reached over `ssh -L`) for a surface.
18
18
  """
19
- __version__ = "1.0.0"
19
+ # Read the real installed version from package metadata so `--version` never goes stale again.
20
+ try:
21
+ from importlib.metadata import version as _pkg_version, PackageNotFoundError
22
+ try:
23
+ __version__ = _pkg_version("axiometa-cli")
24
+ except PackageNotFoundError:
25
+ __version__ = "0.0.0+dev"
26
+ except Exception:
27
+ __version__ = "0.0.0+dev"
@@ -15,6 +15,7 @@ import os
15
15
  import shutil
16
16
  import subprocess
17
17
  import sys
18
+ import time
18
19
  from dataclasses import dataclass, field
19
20
  from pathlib import Path
20
21
 
@@ -233,15 +234,37 @@ _NO_MP_MSG = ("the board is not responding to mpremote — it has no MicroPython
233
234
  "Lay MicroPython down over SWD first: axiometa flash --firmware <micropython.uf2>")
234
235
 
235
236
 
237
+ def _reset_via_run(swd: dict | None) -> bool:
238
+ """Pulse the RP2040 RUN pin (a Pi GPIO) to drop the board into a FRESH REPL before mpremote talks
239
+ to it. The REPL is single-owner: a program from a prior `run` (or a wedged loop) holds the port, so
240
+ the next deploy can't enter raw REPL and surfaces as "not responding to mpremote". A hardware reset
241
+ over RUN clears that every time — it is what a manual power-cycle did by hand. Pi-only (`pinctrl`),
242
+ best-effort and silent where RUN isn't wired or pinctrl is absent (USB boards reset over USB instead,
243
+ so they never reach here needing it)."""
244
+ if not swd:
245
+ return False
246
+ run_gpio = swd.get("run_gpio")
247
+ if not run_gpio or not shutil.which("pinctrl"):
248
+ return False
249
+ if not _run(["pinctrl", "set", str(run_gpio), "op", "dl"], timeout=5).ok:
250
+ return False
251
+ time.sleep(0.3)
252
+ _run(["pinctrl", "set", str(run_gpio), "op", "dh"], timeout=5)
253
+ time.sleep(1.2) # let MicroPython boot through _boot.py to a reachable REPL
254
+ return True
255
+
256
+
236
257
  def _mpremote_reachable(target: list) -> bool:
237
258
  """Quick check that a MicroPython REPL is actually there, so a bare board gives a clear message
238
259
  instead of a cryptic connect error."""
239
260
  return _run(["mpremote", *target, "eval", "1"], timeout=10).ok
240
261
 
241
262
 
242
- def mpremote_run(program: Path, port: str | None, companions: list, on_line=None) -> DeployResult:
263
+ def mpremote_run(program: Path, port: str | None, companions: list, swd: dict | None = None,
264
+ on_line=None) -> DeployResult:
243
265
  """Ephemeral test: copy to RAM and run live. Gone on reset. The fast generate-run-read-fix loop."""
244
266
  target = ["connect", port] if port else []
267
+ _reset_via_run(swd) # fresh REPL first — a prior `run` holds the single-owner port otherwise
245
268
  if not _mpremote_reachable(target):
246
269
  return DeployResult(False, _NO_MP_MSG, [])
247
270
  for cmp in companions:
@@ -249,9 +272,11 @@ def mpremote_run(program: Path, port: str | None, companions: list, on_line=None
249
272
  return _run(["mpremote", *target, "run", str(program)], on_line=on_line)
250
273
 
251
274
 
252
- def mpremote_install(program: Path, port: str | None, companions: list, on_line=None) -> DeployResult:
275
+ def mpremote_install(program: Path, port: str | None, companions: list, swd: dict | None = None,
276
+ on_line=None) -> DeployResult:
253
277
  """Persist: copy companions + main.py to flash, soft-reset so it auto-runs on boot."""
254
278
  target = ["connect", port] if port else []
279
+ _reset_via_run(swd) # fresh REPL first (same single-owner reason as mpremote_run)
255
280
  if not _mpremote_reachable(target):
256
281
  return DeployResult(False, _NO_MP_MSG, [])
257
282
  for cmp in companions:
@@ -385,9 +410,10 @@ def deploy(board: dict, sub: dict, project_dir: Path, verb: str, port: str | Non
385
410
  return DeployResult(False, f"no {sub['device_program']} in {project_dir} — generate it first.", [])
386
411
  if verb in ("run", "install"):
387
412
  companions = _companions(board, sub, project_dir)
413
+ swd = (sub.get("host_interface") or {}).get("swd")
388
414
  if verb == "run":
389
- return mpremote_run(program, port, companions, on_line)
390
- return mpremote_install(program, port, companions, on_line)
415
+ return mpremote_run(program, port, companions, swd, on_line)
416
+ return mpremote_install(program, port, companions, swd, on_line)
391
417
  if verb == "deploy":
392
418
  if not sub.get("fqbn"):
393
419
  return DeployResult(False, "this substrate has no fqbn; use run/install via mpremote.", [])
@@ -57,20 +57,26 @@ def uf2_to_bin(uf2_path: Path) -> tuple[Path, int]:
57
57
 
58
58
 
59
59
  def _interface_cfg(swd: dict) -> str:
60
- """An OpenOCD interface config snippet for the SWD wiring (env-overridable)."""
60
+ """An OpenOCD interface config snippet for the SWD wiring (env-overridable).
61
+
62
+ RUN (GPIO23) is deliberately NOT given to OpenOCD as `srst`. The RP2040's SW-DP is a multidrop
63
+ port and RUN resets the debug port itself: asserting srst during `program ... reset` drops the DAP
64
+ mid-flash and floods "Too long SWD WAIT" until OpenOCD aborts (cores examine fine, then the reset
65
+ step fails). The program flow uses the Cortex-M software reset (SYSRESETREQ) instead, which leaves
66
+ the DAP alive. RUN stays wired for the runtime mpremote reset (see module docstring) — it is just
67
+ wrong to drive it as srst while flashing this chip.
68
+ """
61
69
  driver = os.environ.get("AXIOMETA_OPENOCD_DRIVER", "bcm2835gpio")
62
70
  swclk = swd.get("swclk_gpio", 25)
63
71
  swdio = swd.get("swdio_gpio", 24)
64
- srst = swd.get("run_gpio", 23)
65
72
  speed = os.environ.get("AXIOMETA_OPENOCD_SPEED", "1000")
66
73
  lines = [
67
74
  f"adapter driver {driver}",
68
75
  f"adapter gpio swclk {swclk}",
69
76
  f"adapter gpio swdio {swdio}",
77
+ "transport select swd",
78
+ f"adapter speed {speed}",
70
79
  ]
71
- if srst:
72
- lines += [f"adapter gpio srst {srst}", "reset_config srst_only srst_nogate"]
73
- lines += ["transport select swd", f"adapter speed {speed}"]
74
80
  return "\n".join(lines) + "\n"
75
81
 
76
82
 
@@ -392,12 +392,18 @@ def run(args) -> None:
392
392
  if not text:
393
393
  continue
394
394
  low = text.lower()
395
- if low in ("/exit", "/quit", "exit", "quit"):
395
+ # Commands work WITH or WITHOUT the leading slash, and tolerate a trailing "?": a lone word like
396
+ # "run" only ever means "deploy it", never a build prompt. `cmd` is the bare single token (empty
397
+ # if the user typed a real sentence), so "run faster" still goes to Axie as a build request.
398
+ cmd = low.lstrip("/").rstrip("? ").strip()
399
+ if " " in cmd:
400
+ cmd = ""
401
+ if cmd in ("exit", "quit"):
396
402
  break
397
- if low == "/help":
403
+ if cmd == "help":
398
404
  ui.hint("describe a build in plain words, or: /flash /deploy /run /install /host /monitor /serve /board /exit")
399
405
  continue
400
- if low.startswith("/flash"):
406
+ if cmd == "flash" or low.startswith("/flash"):
401
407
  parts = text.split()
402
408
  fw = Path(parts[1]).expanduser() if len(parts) > 1 else None
403
409
  res = core.flash_firmware(board, sub, fw, on_line=lambda ln: print(ui.c(" " + ln, "grey")))
@@ -405,20 +411,19 @@ def run(args) -> None:
405
411
  if not res.ok and fw is None:
406
412
  ui.hint("usage: /flash <path-to-micropython.uf2>")
407
413
  continue
408
- if low in ("/deploy", "/run", "/install"):
409
- verb = low[1:]
410
- res = core.deploy(board, sub, pdir, verb if verb in core.verbs_for(sub) else verb, port,
414
+ if cmd in ("deploy", "run", "install"):
415
+ res = core.deploy(board, sub, pdir, cmd, port,
411
416
  on_line=lambda ln: print(ui.c(" " + ln, "grey")))
412
417
  (ui.ok if res.ok else ui.err)(res.detail.splitlines()[0] if res.ok and res.detail else res.detail)
413
418
  continue
414
- if low == "/host":
419
+ if cmd == "host":
415
420
  run_host(pdir); continue
416
- if low == "/monitor":
421
+ if cmd == "monitor":
417
422
  monitor(port); continue
418
- if low == "/serve":
423
+ if cmd == "serve":
419
424
  from . import serve as serve_mod
420
425
  serve_mod.serve(pdir, port, over_ssh=profile["over_ssh"]); continue
421
- if low == "/board":
426
+ if cmd == "board":
422
427
  board, sub, port = _pick_target(pdir, boards, devices)
423
428
  session.board_id = board["id"]
424
429
  ui.ok(f"target: {board['name']} · {sub['label']}"); continue
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: axiometa-cli
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: Prompt to hardware, in your terminal: detect a board, chat to build, deploy, watch serial.
5
5
  Author-email: Axiometa <dr.dumcius@gmail.com>
6
6
  Project-URL: Homepage, https://axiometa.io
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "axiometa-cli"
3
- version = "1.0.2"
3
+ version = "1.0.4"
4
4
  description = "Prompt to hardware, in your terminal: detect a board, chat to build, deploy, watch serial."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
File without changes
File without changes