@caddis/cli 0.4.16 → 0.4.19
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.
- package/bundles/antigravity-plugin/caddis_statusline.py +91 -14
- package/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/scripts/caddis_adhoc.py +530 -0
- package/bundles/antigravity-plugin/scripts/caddis_lanes.py +8 -0
- package/bundles/antigravity-plugin/scripts/caddis_statusline.py +91 -14
- package/bundles/antigravity-plugin/skills/_registry.md +3 -0
- package/bundles/antigravity-plugin/skills/hld/SKILL.md +27 -0
- package/bundles/antigravity-plugin/skills/lld/SKILL.md +30 -0
- package/bundles/antigravity-plugin/skills/mmr/SKILL.md +87 -0
- package/bundles/antigravity-plugin-extras/plugin.json +1 -1
- package/bundles/antigravity-plugin-extras/skills/_registry.md +2 -0
- package/bundles/antigravity-plugin-extras/skills/hld/SKILL.md +100 -0
- package/bundles/antigravity-plugin-extras/skills/hld/scripts/convert_doc_to_confluence.py +714 -0
- package/bundles/antigravity-plugin-extras/skills/lld/SKILL.md +94 -0
- package/bundles/antigravity-plugin-extras/skills/lld/scripts/convert_doc_to_confluence.py +714 -0
- package/bundles/manifest.json +3 -3
- package/package.json +1 -1
|
@@ -38,8 +38,22 @@ import shutil
|
|
|
38
38
|
import subprocess
|
|
39
39
|
import sys
|
|
40
40
|
import time
|
|
41
|
+
import datetime
|
|
42
|
+
import traceback
|
|
41
43
|
from pathlib import Path
|
|
42
44
|
|
|
45
|
+
_DEBUG_LOG = Path.home() / ".caddis" / "statusline_debug.log"
|
|
46
|
+
def _debug_log(msg: str):
|
|
47
|
+
if not os.environ.get("CADDIS_STATUSLINE_DEBUG"):
|
|
48
|
+
return
|
|
49
|
+
try:
|
|
50
|
+
with open(_DEBUG_LOG, "a", encoding="utf-8") as f:
|
|
51
|
+
f.write(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')}] [PID {os.getpid()}] {msg}\n")
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
_debug_log(f"INVOKE: argv={sys.argv} cwd={os.getcwd()}")
|
|
56
|
+
|
|
43
57
|
# --------------------------------------------------------------------------------------
|
|
44
58
|
# Config
|
|
45
59
|
# --------------------------------------------------------------------------------------
|
|
@@ -186,17 +200,30 @@ def term_width() -> int:
|
|
|
186
200
|
# Host facts the payload does not carry
|
|
187
201
|
# --------------------------------------------------------------------------------------
|
|
188
202
|
|
|
203
|
+
_GIT_CACHE_FILE = Path.home() / ".caddis" / ".git_status_cache.json"
|
|
204
|
+
|
|
205
|
+
|
|
189
206
|
def git_facts(cwd: str) -> dict:
|
|
190
207
|
"""One `git status --porcelain=v2 --branch` call -> branch, staged/unstaged/untracked,
|
|
191
|
-
ahead/behind.
|
|
192
|
-
|
|
208
|
+
ahead/behind. Uses a 2-second cache file per cwd to eliminate git process thrashing on rapid
|
|
209
|
+
status updates."""
|
|
193
210
|
out = {"branch": "", "staged": 0, "unstaged": 0, "untracked": 0, "ahead": 0, "behind": 0}
|
|
194
211
|
if not cwd or not os.path.isdir(cwd):
|
|
195
212
|
return out
|
|
213
|
+
|
|
214
|
+
now = time.time()
|
|
215
|
+
try:
|
|
216
|
+
if _GIT_CACHE_FILE.exists():
|
|
217
|
+
cached = json.loads(_GIT_CACHE_FILE.read_text(encoding="utf-8"))
|
|
218
|
+
if cached.get("cwd") == cwd and (now - cached.get("time", 0)) < 2.0:
|
|
219
|
+
return cached.get("facts", out)
|
|
220
|
+
except Exception:
|
|
221
|
+
pass
|
|
222
|
+
|
|
196
223
|
try:
|
|
197
224
|
proc = subprocess.run(
|
|
198
225
|
["git", "-C", cwd, "--no-optional-locks", "status", "--porcelain=v2", "--branch"],
|
|
199
|
-
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=
|
|
226
|
+
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=0.8,
|
|
200
227
|
)
|
|
201
228
|
text = proc.stdout.decode("utf-8", "replace")
|
|
202
229
|
except Exception:
|
|
@@ -223,6 +250,12 @@ def git_facts(cwd: str) -> dict:
|
|
|
223
250
|
out["unstaged"] += 1
|
|
224
251
|
elif line.startswith("? "):
|
|
225
252
|
out["untracked"] += 1
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
_GIT_CACHE_FILE.write_text(json.dumps({"cwd": cwd, "time": now, "facts": out}), encoding="utf-8")
|
|
256
|
+
except Exception:
|
|
257
|
+
pass
|
|
258
|
+
|
|
226
259
|
return out
|
|
227
260
|
|
|
228
261
|
|
|
@@ -722,7 +755,19 @@ def wire_host(host: str, settings_path: Path, command: str, dry_run: bool) -> in
|
|
|
722
755
|
if host == "agy":
|
|
723
756
|
entry["enabled"] = True # agy ignores a statusLine block without this
|
|
724
757
|
|
|
725
|
-
|
|
758
|
+
changed = False
|
|
759
|
+
if data.get("statusLine") != entry:
|
|
760
|
+
data["statusLine"] = entry
|
|
761
|
+
changed = True
|
|
762
|
+
|
|
763
|
+
if host == "agy":
|
|
764
|
+
title_cmd = command.replace("--profile agy", "--title")
|
|
765
|
+
title_entry = {"type": "command", "command": title_cmd, "enabled": True}
|
|
766
|
+
if data.get("title") != title_entry:
|
|
767
|
+
data["title"] = title_entry
|
|
768
|
+
changed = True
|
|
769
|
+
|
|
770
|
+
if not changed:
|
|
726
771
|
print(" ok %-6s already wired" % host)
|
|
727
772
|
return 0
|
|
728
773
|
|
|
@@ -730,7 +775,6 @@ def wire_host(host: str, settings_path: Path, command: str, dry_run: bool) -> in
|
|
|
730
775
|
if settings_path.exists():
|
|
731
776
|
backup = settings_path.with_suffix(settings_path.suffix + ".bak-caddis-statusline")
|
|
732
777
|
shutil.copyfile(str(settings_path), str(backup))
|
|
733
|
-
data["statusLine"] = entry
|
|
734
778
|
settings_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
735
779
|
print(" ok %-6s wired -> %s" % (host, settings_path))
|
|
736
780
|
return 0
|
|
@@ -761,6 +805,12 @@ def check() -> int:
|
|
|
761
805
|
else:
|
|
762
806
|
print(" !! %-6s points elsewhere: %s" % (host, cmd))
|
|
763
807
|
bad += 1
|
|
808
|
+
if host == "agy":
|
|
809
|
+
t_entry = data.get("title")
|
|
810
|
+
if isinstance(t_entry, dict) and "statusline.py" in str(t_entry.get("command", "")) and "--title" in str(t_entry.get("command", "")):
|
|
811
|
+
print(" ok agy-title %s" % t_entry.get("command"))
|
|
812
|
+
else:
|
|
813
|
+
print(" -- agy-title not wired to statusline.py (run --install to wire)")
|
|
764
814
|
return bad
|
|
765
815
|
|
|
766
816
|
|
|
@@ -815,6 +865,7 @@ def main(argv=None) -> int:
|
|
|
815
865
|
parser.add_argument("--sample", action="store_true", help="render a fake payload (preview / test)")
|
|
816
866
|
parser.add_argument("--dry-run", action="store_true", help="with --install: print, write nothing")
|
|
817
867
|
parser.add_argument("--no-color", action="store_true", help="render without ANSI colour")
|
|
868
|
+
parser.add_argument("--title", action="store_true", help="render terminal title (agy)")
|
|
818
869
|
args = parser.parse_args(argv)
|
|
819
870
|
|
|
820
871
|
# The line is Unicode. On Windows the default cp1252 stdout raises on ◆/⎇/█.
|
|
@@ -827,6 +878,21 @@ def main(argv=None) -> int:
|
|
|
827
878
|
except Exception:
|
|
828
879
|
pass
|
|
829
880
|
|
|
881
|
+
if args.title:
|
|
882
|
+
cwd = os.getcwd()
|
|
883
|
+
repo = os.path.basename(cwd) or cwd
|
|
884
|
+
facts = git_facts(cwd)
|
|
885
|
+
branch = facts.get("branch", "")
|
|
886
|
+
if branch:
|
|
887
|
+
print(f"agy: {repo} [{branch}]")
|
|
888
|
+
else:
|
|
889
|
+
print(f"agy: {repo}")
|
|
890
|
+
try:
|
|
891
|
+
sys.stdout.flush()
|
|
892
|
+
except Exception:
|
|
893
|
+
pass
|
|
894
|
+
return 0
|
|
895
|
+
|
|
830
896
|
if args.install:
|
|
831
897
|
hosts = ["claude", "agy"] if args.host == "both" else [args.host]
|
|
832
898
|
return 1 if install(hosts, args.dry_run) else 0
|
|
@@ -838,31 +904,39 @@ def main(argv=None) -> int:
|
|
|
838
904
|
if args.no_color:
|
|
839
905
|
cfg["color"] = False
|
|
840
906
|
|
|
907
|
+
t0 = time.time()
|
|
841
908
|
if args.sample:
|
|
842
909
|
data = SAMPLES[args.profile]
|
|
843
910
|
else:
|
|
844
911
|
try:
|
|
845
912
|
raw = sys.stdin.read()
|
|
846
|
-
except Exception:
|
|
913
|
+
except Exception as e:
|
|
914
|
+
_debug_log(f"stdin.read exception: {e}")
|
|
847
915
|
raw = ""
|
|
916
|
+
_debug_log(f"stdin read: {len(raw)} bytes in {time.time()-t0:.3f}s")
|
|
848
917
|
start = raw.find("{") # strip a UTF-8 BOM or any leading noise
|
|
849
918
|
try:
|
|
850
919
|
data = json.loads(raw[start:]) if start >= 0 else {}
|
|
851
|
-
except Exception:
|
|
920
|
+
except Exception as e:
|
|
921
|
+
_debug_log(f"json.loads exception: {e}")
|
|
852
922
|
data = {}
|
|
853
923
|
if not isinstance(data, dict):
|
|
854
924
|
data = {}
|
|
855
925
|
|
|
856
926
|
try:
|
|
857
927
|
line = RENDERERS[args.profile](data, cfg)
|
|
858
|
-
|
|
928
|
+
_debug_log(f"Rendered in {time.time()-t0:.3f}s")
|
|
929
|
+
except Exception as e:
|
|
930
|
+
_debug_log(f"Renderer exception: {traceback.format_exc()}")
|
|
859
931
|
# A status line must never break the host. Degrade to the directory name.
|
|
860
932
|
line = os.path.basename(os.getcwd())
|
|
861
933
|
|
|
862
934
|
try:
|
|
863
935
|
print(line)
|
|
864
936
|
sys.stdout.flush()
|
|
865
|
-
|
|
937
|
+
_debug_log(f"Printed and flushed in {time.time()-t0:.3f}s")
|
|
938
|
+
except (BrokenPipeError, OSError) as e:
|
|
939
|
+
_debug_log(f"BrokenPipeError on flush: {e}")
|
|
866
940
|
# Parent process cancelled or closed stdout pipe (e.g. agy runner on user prompt/state switch).
|
|
867
941
|
# Redirecting to devnull prevents Windows Python from throwing unhandled TextIOWrapper errors on exit.
|
|
868
942
|
try:
|
|
@@ -871,7 +945,8 @@ def main(argv=None) -> int:
|
|
|
871
945
|
except Exception:
|
|
872
946
|
pass
|
|
873
947
|
return 0
|
|
874
|
-
except Exception:
|
|
948
|
+
except Exception as e:
|
|
949
|
+
_debug_log(f"Flush exception: {e}")
|
|
875
950
|
return 0
|
|
876
951
|
|
|
877
952
|
return 0
|
|
@@ -880,10 +955,12 @@ def main(argv=None) -> int:
|
|
|
880
955
|
if __name__ == "__main__":
|
|
881
956
|
try:
|
|
882
957
|
code = main()
|
|
883
|
-
except BaseException:
|
|
958
|
+
except BaseException as e:
|
|
959
|
+
_debug_log(f"Unhandled exception in main: {traceback.format_exc()}")
|
|
884
960
|
code = 0
|
|
885
961
|
try:
|
|
886
962
|
sys.stdout.flush()
|
|
887
|
-
except Exception:
|
|
888
|
-
|
|
889
|
-
|
|
963
|
+
except Exception as e:
|
|
964
|
+
_debug_log(f"Final flush exception: {e}")
|
|
965
|
+
_debug_log(f"EXIT with code {code}")
|
|
966
|
+
os._exit(code)
|