Skip to content

Commit 3494dea

Browse files
committed
feat: turtle-context, noe shell cmd, ctx-aware |?, CMD+SHIFT+X/B, BearBrowser overlay live
1 parent a5550d5 commit 3494dea

3 files changed

Lines changed: 366 additions & 30 deletions

File tree

assets/sourceos/bin/turtle-context

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
#!/usr/bin/env python3
2+
"""turtle-context — snapshot of active context across the SourceOS stack.
3+
4+
Reads from:
5+
memory-mesh/active.json — current cwd/branch/title
6+
memory-mesh/context.jsonl — last N mesh events
7+
~/.local/state/sourceos/status/ — CI/PR/Noetica/board state
8+
BearBrowser memory candidates — last browsed pages w/ agent context
9+
~/notes/*.md — most recently modified notes
10+
11+
Usage:
12+
turtle-context # pretty snapshot
13+
turtle-context --json # machine-readable JSON
14+
turtle-context --short # one-liner summary for use in prompts
15+
"""
16+
from __future__ import annotations
17+
18+
import datetime
19+
import json
20+
import os
21+
import sys
22+
from pathlib import Path
23+
24+
MESH_DIR = Path(os.getenv("XDG_STATE_HOME", str(Path.home() / ".local/state"))) / "sourceos" / "memory-mesh"
25+
STATUS_DIR = Path(os.getenv("XDG_STATE_HOME", str(Path.home() / ".local/state"))) / "sourceos" / "status"
26+
NOTES_DIR = Path(os.getenv("GOOSE_NOTES_DIR", str(Path.home() / "notes")))
27+
BB_SUPPORT = Path.home() / "Library" / "Application Support" / "BearBrowser"
28+
29+
C_RESET = "\033[0m"
30+
C_BOLD = "\033[1m"
31+
C_DIM = "\033[2m"
32+
C_BLUE = "\033[38;2;88;166;255m"
33+
C_TEAL = "\033[38;2;63;185;80m"
34+
C_PURPLE = "\033[38;2;188;140;255m"
35+
C_CYAN = "\033[38;2;0;200;200m"
36+
C_YELLOW = "\033[38;2;210;153;34m"
37+
C_WHITE = "\033[38;2;230;237;243m"
38+
C_GREY = "\033[38;2;139;148;158m"
39+
C_ORANGE = "\033[38;2;255;123;114m"
40+
41+
42+
def load_json(path: Path) -> dict:
43+
if not path.exists():
44+
return {}
45+
try:
46+
return json.loads(path.read_text())
47+
except Exception:
48+
return {}
49+
50+
51+
def load_jsonl_tail(path: Path, n: int = 10) -> list[dict]:
52+
if not path.exists():
53+
return []
54+
items = []
55+
try:
56+
for line in path.read_text(errors="replace").splitlines()[-n * 3:]:
57+
try:
58+
items.append(json.loads(line))
59+
except Exception:
60+
pass
61+
except Exception:
62+
pass
63+
return items[-n:]
64+
65+
66+
def age_str(iso: str) -> str:
67+
if not iso:
68+
return ""
69+
try:
70+
t = datetime.datetime.fromisoformat(iso.replace("Z", "+00:00"))
71+
s = int((datetime.datetime.now(datetime.timezone.utc) - t).total_seconds())
72+
if s < 60:
73+
return f"{s}s ago"
74+
if s < 3600:
75+
return f"{s//60}m ago"
76+
return f"{s//3600}h ago"
77+
except Exception:
78+
return ""
79+
80+
81+
def gather() -> dict:
82+
active = load_json(MESH_DIR / "active.json")
83+
mesh_tail = load_jsonl_tail(MESH_DIR / "context.jsonl", 8)
84+
ci = load_json(STATUS_DIR / "ci.json")
85+
pr = load_json(STATUS_DIR / "pr.json")
86+
noetica = load_json(STATUS_DIR / "noetica.json")
87+
board = load_json(STATUS_DIR / "board.json")
88+
89+
# BearBrowser recent activity (last 3 candidates)
90+
bb_cands: list[dict] = []
91+
bb_cand_path = BB_SUPPORT / "memory" / "candidates.jsonl"
92+
if bb_cand_path.exists():
93+
for line in bb_cand_path.read_text(errors="replace").splitlines()[-30:]:
94+
try:
95+
bb_cands.append(json.loads(line))
96+
except Exception:
97+
pass
98+
bb_cands = bb_cands[-3:]
99+
100+
# Recent notes
101+
notes: list[str] = []
102+
if NOTES_DIR.exists():
103+
md_files = sorted(NOTES_DIR.glob("*.md"), key=lambda p: p.stat().st_mtime, reverse=True)
104+
for p in md_files[:3]:
105+
notes.append(p.name)
106+
107+
return {
108+
"active": active,
109+
"mesh": mesh_tail,
110+
"ci": ci,
111+
"pr": pr,
112+
"noetica": noetica,
113+
"board": board,
114+
"bb_cands": bb_cands,
115+
"notes": notes,
116+
}
117+
118+
119+
def print_pretty(ctx: dict) -> None:
120+
def hr(w: int = 68) -> str:
121+
return C_GREY + "─" * w + C_RESET
122+
123+
print()
124+
print(f" {C_BLUE}{C_BOLD}◆ SourceOS Context{C_RESET} {C_GREY}{datetime.datetime.now().strftime('%H:%M:%S')}{C_RESET}")
125+
print(f" {hr()}")
126+
127+
# Active focus
128+
active = ctx["active"]
129+
if active:
130+
cwd = active.get("cwd", "")
131+
branch = active.get("branch", "")
132+
title = active.get("title", "")
133+
upd = age_str(active.get("updated", ""))
134+
print(f" {C_BLUE}FOCUS{C_RESET} {C_GREY}{upd}{C_RESET}")
135+
if cwd:
136+
print(f" {C_WHITE}cwd: {C_RESET}{cwd}")
137+
if branch:
138+
print(f" {C_TEAL}branch: {C_RESET}{branch}")
139+
if title:
140+
print(f" {C_GREY}title: {C_RESET}{title}")
141+
else:
142+
print(f" {C_DIM} — no active context (use tc to capture){C_RESET}")
143+
print(f" {hr()}")
144+
145+
# Services
146+
noe = ctx["noetica"]
147+
ci = ctx["ci"]
148+
pr = ctx["pr"]
149+
bd = ctx["board"]
150+
151+
noe_s = (C_TEAL + "● Noetica ok") if noe.get("ok") else (C_ORANGE + "○ Noetica down")
152+
noe_d = f" brain={noe.get('brain','?')} queries={noe.get('queries','?')}" if noe.get("ok") else ""
153+
print(f" {noe_s}{C_GREY}{noe_d}{C_RESET}")
154+
155+
if ci:
156+
conc = ci.get("conclusion", "")
157+
ci_c = C_TEAL if conc == "success" else (C_ORANGE if conc == "failure" else C_YELLOW)
158+
print(f" {ci_c}● CI {ci.get('status','?')} / {conc or '…'}{C_RESET} {C_GREY}{ci.get('name','?')}{C_RESET}")
159+
160+
if pr:
161+
print(f" {C_YELLOW}● PRs {pr.get('count', 0)} open{C_RESET} {C_GREY}{pr.get('repo','')}{C_RESET}")
162+
163+
if bd:
164+
score = bd.get("score", 0)
165+
print(f" {C_PURPLE}● Board {score:.1f}%{C_RESET} {C_GREY}{age_str(bd.get('ts',''))}{C_RESET}")
166+
167+
print(f" {hr()}")
168+
169+
# Memory mesh events
170+
mesh = ctx["mesh"]
171+
if mesh:
172+
print(f" {C_PURPLE}MESH{C_RESET} {C_GREY}(last {len(mesh)} events){C_RESET}")
173+
for ev in reversed(mesh):
174+
ts = ev.get("ts", "")[:16]
175+
kind = ev.get("kind", "?")[:10]
176+
src = ev.get("source", "?")[:10]
177+
ttl = (ev.get("title") or ev.get("content", ""))[:52]
178+
print(f" {C_PURPLE}·{C_RESET} {C_GREY}{ts} {C_YELLOW}{kind:<10}{C_GREY}[{src}]{C_RESET} {C_WHITE}{ttl}{C_RESET}")
179+
print(f" {hr()}")
180+
181+
# BearBrowser
182+
bb_cands = ctx["bb_cands"]
183+
if bb_cands:
184+
print(f" {C_CYAN}BEARBROWSER{C_RESET} {C_GREY}memory candidates{C_RESET}")
185+
for c in reversed(bb_cands):
186+
content = c.get("content", {})
187+
ttl = (content.get("title", "") if isinstance(content, dict) else "?")[:52]
188+
status = c.get("status", "proposed")
189+
ts = c.get("timestamp", "")[:16]
190+
sc = C_TEAL if status == "committed" else C_YELLOW
191+
print(f" {sc}{'✓' if status=='committed' else '·'}{C_RESET} {C_GREY}{ts}{C_RESET} {C_WHITE}{ttl}{C_RESET}")
192+
print(f" {hr()}")
193+
194+
# Notes
195+
notes = ctx["notes"]
196+
if notes:
197+
print(f" {C_BLUE}NOTES{C_RESET} {C_GREY}(recent){C_RESET}")
198+
for n in notes:
199+
print(f" {C_GREY}·{C_RESET} {n}")
200+
201+
print()
202+
203+
204+
def print_short(ctx: dict) -> None:
205+
"""Single-line summary for injecting into prompts."""
206+
active = ctx["active"]
207+
parts = []
208+
if active.get("cwd"):
209+
parts.append(f"cwd={active['cwd']}")
210+
if active.get("branch"):
211+
parts.append(f"branch={active['branch']}")
212+
noe = ctx["noetica"]
213+
parts.append(f"noetica={'up' if noe.get('ok') else 'down'}")
214+
ci = ctx["ci"]
215+
if ci:
216+
parts.append(f"ci={ci.get('conclusion', ci.get('status','?'))}")
217+
mesh = ctx["mesh"]
218+
if mesh:
219+
last = mesh[-1]
220+
parts.append(f"last_event={last.get('kind','?')}:{(last.get('title') or '')[:30]}")
221+
print(" ".join(parts))
222+
223+
224+
def main() -> None:
225+
args = sys.argv[1:]
226+
ctx = gather()
227+
228+
if "--json" in args:
229+
print(json.dumps(ctx, indent=2, default=str))
230+
elif "--short" in args:
231+
print_short(ctx)
232+
else:
233+
print_pretty(ctx)
234+
235+
236+
if __name__ == "__main__":
237+
main()

0 commit comments

Comments
 (0)