@caddis/cli 0.4.61 → 0.4.62

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.
@@ -71,8 +71,9 @@ DEFAULT_CONFIG = {
71
71
  "bar_width": 8,
72
72
  "token_counts": True, # ◉ 412k/1M — off falls back to a bare ctx:41%
73
73
  "cached_tokens": True, # ⚡38k cached — rendered LOUD, see render_claude()
74
- "ram": True, # 💻 69% (44 GB)
75
- "clock": True, # ◷ 03:41
74
+ "ram": False, # 💻 69% (44 GB) - off by default (owner, 20 Oct 2026)
75
+ "thinking": False, # Claude's "thinking" flag, agy's "thinking" state - off by default (20 Oct 2026)
76
+ "clock": False, # ◷ 03:41 - off by default (owner, 20 Oct 2026)
76
77
  "relay": True, # ⏺ relay (a parked resume pointer exists)
77
78
  "rate_limits": True, # 5h:12% 7d:40%
78
79
  "session_id": False, # #a1b2c3d4
@@ -468,7 +469,7 @@ def cached_segment(c: C, cfg: dict, cached: int) -> str:
468
469
 
469
470
 
470
471
  def ram_segment(c: C, cfg: dict) -> str:
471
- if not cfg.get("ram", True):
472
+ if not cfg.get("ram", False):
472
473
  return ""
473
474
  min_w = as_int(cfg.get("ram_min_width"), 0)
474
475
  if min_w and term_width() < min_w:
@@ -482,7 +483,7 @@ def ram_segment(c: C, cfg: dict) -> str:
482
483
 
483
484
 
484
485
  def clock_segment(c: C, cfg: dict) -> str:
485
- if not cfg.get("clock", True):
486
+ if not cfg.get("clock", False):
486
487
  return ""
487
488
  return c.dim + icon(cfg, "clock") + time.strftime("%H:%M") + c.reset
488
489
 
@@ -549,7 +550,7 @@ def render_claude(data: dict, cfg: dict) -> str:
549
550
  if style and str(style).lower() != "default":
550
551
  parts.append(c.blue + "style:" + str(style) + c.reset)
551
552
 
552
- if dig(data, "thinking", "enabled") is True:
553
+ if cfg.get("thinking", False) and dig(data, "thinking", "enabled") is True:
553
554
  parts.append(c.magenta + "thinking" + c.reset)
554
555
 
555
556
  if cfg.get("rate_limits", True):
@@ -581,7 +582,7 @@ def render_claude(data: dict, cfg: dict) -> str:
581
582
 
582
583
  session_name = dig(data, "session_name")
583
584
  if session_name:
584
- parts.append(c.dim + '"' + str(session_name) + '"' + c.reset)
585
+ parts.append(c.bold + c.magenta + str(session_name) + c.reset) # bright, so the session is easy to spot
585
586
 
586
587
  if cfg.get("session_id", False):
587
588
  sid = str(dig(data, "session_id") or "")
@@ -616,6 +617,8 @@ def render_agy(data: dict, cfg: dict) -> str:
616
617
  parts.append(c.white + c.bold + "agy" + c.reset)
617
618
 
618
619
  state = str(dig(data, "agent_state") or dig(data, "execution_mode") or "").lower()
620
+ if state == "thinking" and not cfg.get("thinking", False):
621
+ state = "" # working and idle still show
619
622
  if state:
620
623
  label, col = {
621
624
  "working": (icon(cfg, "bolt") + "working", c.green),
@@ -704,6 +707,61 @@ HOST_SETTINGS = {
704
707
  }
705
708
 
706
709
 
710
+ VERSION_STAMP = INSTALL_DIR / "statusline.version"
711
+
712
+
713
+ def plugin_version(here: Path | None = None) -> str:
714
+ """The caddis version of the plugin this file ships in: `.claude-plugin/plugin.json` or a root `plugin.json`
715
+ one folder above `scripts/`. "" in a source checkout or when unreadable."""
716
+ base = Path(here or Path(__file__).resolve().parent).parent
717
+ for manifest in (base / ".claude-plugin" / "plugin.json", base / "plugin.json"):
718
+ try:
719
+ data = json.loads(manifest.read_text(encoding="utf-8"))
720
+ except Exception:
721
+ continue
722
+ if isinstance(data, dict) and str(data.get("name", "")).startswith("caddis") and data.get("version"):
723
+ return str(data["version"])
724
+ return ""
725
+
726
+
727
+ def _version_key(text: str):
728
+ parts = str(text or "").strip().split(".")
729
+ return tuple(int(x) for x in parts) if parts and all(x.isdigit() for x in parts) else None
730
+
731
+
732
+ def refresh_installed(src: Path | None = None, version: str | None = None) -> bool:
733
+ """Bring the installed renderer (`~/.caddis/statusline.py`) up to this plugin's copy.
734
+
735
+ Why (20 Oct 2026): only `--install` copied the renderer, so a caddis update never reached it. This machine
736
+ kept a copy whose `git` calls opened a console window each time agy drew its status line.
737
+ Rules: nothing happens unless the status line was installed; the copy happens only when this plugin's version
738
+ is newer than the one stamped at the last copy (an older session never downgrades it); a source checkout
739
+ (no plugin version) never copies. Returns True when it copied."""
740
+ src = Path(src or Path(__file__).resolve())
741
+ version = plugin_version() if version is None else version
742
+ if not INSTALLED_SCRIPT.is_file() or _version_key(version) is None:
743
+ return False
744
+ try:
745
+ stamped = VERSION_STAMP.read_text(encoding="utf-8").strip()
746
+ except OSError:
747
+ stamped = ""
748
+ old = _version_key(stamped)
749
+ if old is not None and old >= _version_key(version):
750
+ return False
751
+ try:
752
+ if src.resolve() != INSTALLED_SCRIPT.resolve() and src.read_bytes() != INSTALLED_SCRIPT.read_bytes():
753
+ tmp = INSTALLED_SCRIPT.with_name("%s.%d.tmp" % (INSTALLED_SCRIPT.name, os.getpid()))
754
+ shutil.copyfile(str(src), str(tmp))
755
+ os.replace(str(tmp), str(INSTALLED_SCRIPT))
756
+ copied = True
757
+ else:
758
+ copied = False
759
+ VERSION_STAMP.write_text(version + "\n", encoding="utf-8")
760
+ return copied
761
+ except OSError:
762
+ return False # a session start must never fail over the status line
763
+
764
+
707
765
  def python_command() -> str:
708
766
  """The interpreter NAME to write into settings — not an absolute path.
709
767
 
@@ -726,6 +784,14 @@ def install(hosts, dry_run: bool = False) -> int:
726
784
  # because the version is in the path.
727
785
  if not dry_run and src != INSTALLED_SCRIPT.resolve():
728
786
  shutil.copyfile(str(src), str(INSTALLED_SCRIPT))
787
+ if not dry_run:
788
+ # Stamp the version, so a later --refresh from an OLDER plugin session cannot replace this copy.
789
+ version = plugin_version()
790
+ if _version_key(version) is not None:
791
+ try:
792
+ VERSION_STAMP.write_text(version + "\n", encoding="utf-8")
793
+ except OSError:
794
+ pass
729
795
  print("renderer -> %s" % INSTALLED_SCRIPT)
730
796
 
731
797
  if not CONFIG_PATH.exists():
@@ -903,8 +969,14 @@ def main(argv=None) -> int:
903
969
  parser.add_argument("--dry-run", action="store_true", help="with --install: print, write nothing")
904
970
  parser.add_argument("--no-color", action="store_true", help="render without ANSI colour")
905
971
  parser.add_argument("--title", action="store_true", help="render terminal title (agy)")
972
+ parser.add_argument("--refresh", action="store_true",
973
+ help="copy this plugin's renderer over an older installed one (session-start hook)")
906
974
  args = parser.parse_args(argv)
907
975
 
976
+ if args.refresh:
977
+ refresh_installed()
978
+ return 0 # silent and never failing: it runs at every session start
979
+
908
980
  # The line is Unicode. On Windows the default cp1252 stdout raises on ◆/⎇/█.
909
981
  try:
910
982
  sys.stdout.reconfigure(encoding="utf-8", errors="replace")
@@ -43,7 +43,10 @@ def _path(root: Path, workstream: str | None) -> tuple[Path, Path]:
43
43
  artifact = _artifact(root)
44
44
  name = _name(workstream) if workstream is not None else "inbox"
45
45
  folder = artifact / "todo-list"
46
- if folder.is_symlink() or not folder.resolve().is_relative_to(artifact.resolve()):
46
+ # Only a folder that EXISTS can point elsewhere. Resolving one that does not exist yet raced a concurrent
47
+ # writer creating it: on Windows the two paths could resolve differently (a short 8.3 name on one side), and a
48
+ # safe add failed with this error (main CI, 20 Oct 2026, test_concurrent_inbox_add_loses_no_item).
49
+ if folder.is_symlink() or (folder.exists() and not folder.resolve().is_relative_to(artifact.resolve())):
47
50
  raise ValueError("todo-list path escapes artifact root")
48
51
  path = folder / f"{name}.md"
49
52
  # Reads are unlocked, so refuse here as well as in locked_path. On Windows a writer replacing the file
@@ -58,7 +58,9 @@ def _plain_name(value: str) -> str:
58
58
  def _artifact(root: Path) -> Path:
59
59
  project = Path(root).resolve()
60
60
  artifact = Path(root) / ".caddis"
61
- if not artifact.resolve().is_relative_to(project) or artifact.is_symlink():
61
+ # Only an existing .caddis can point elsewhere; resolving one that does not exist yet raced a concurrent writer
62
+ # creating it (see caddis_todo._path, 20 Oct 2026).
63
+ if artifact.is_symlink() or (artifact.exists() and not artifact.resolve().is_relative_to(project)):
62
64
  raise ValueError("artifact root escapes project")
63
65
  return artifact
64
66
 
@@ -1,7 +1,7 @@
1
1
  {
2
- "poolVersion": "1.3.147",
2
+ "poolVersion": "1.3.148",
3
3
  "bundles": {
4
- "antigravity-plugin": "1.3.147",
4
+ "antigravity-plugin": "1.3.148",
5
5
  "antigravity-plugin-extras": "1.3.45",
6
6
  "codex": "unknown"
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caddis/cli",
3
- "version": "0.4.61",
3
+ "version": "0.4.62",
4
4
  "description": "Install, update and diagnose the caddis dev harness across every coding agent you use (Claude Code, agy/Antigravity, and more).",
5
5
  "keywords": [
6
6
  "caddis",