Skip to content

Commit 4cc9f72

Browse files
committed
Add turtle-session CLI scaffold
1 parent 7feb1bb commit 4cc9f72

1 file changed

Lines changed: 161 additions & 0 deletions

File tree

assets/sourceos/bin/turtle-session

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#!/usr/bin/env python3
2+
"""TurtleTerm session ergonomics CLI v0."""
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import datetime as dt
8+
import json
9+
import os
10+
import sys
11+
import uuid
12+
from pathlib import Path
13+
from typing import Any
14+
15+
16+
SCHEMA = "sourceos.turtle.session.response.v0"
17+
18+
19+
def now() -> str:
20+
return dt.datetime.now(dt.timezone.utc).isoformat().replace("+00:00", "Z")
21+
22+
23+
def env(name: str, fallback: str = "") -> str:
24+
value = os.environ.get(name)
25+
return value if value else fallback
26+
27+
28+
def state_dir() -> Path:
29+
return Path(env("TURTLE_SESSION_STATE", str(Path.home() / ".local" / "state" / "sourceos" / "turtle-term")))
30+
31+
32+
def marks_path() -> Path:
33+
return state_dir() / "marks.ndjson"
34+
35+
36+
def events_path() -> Path:
37+
return Path(env("SOURCEOS_TERMINAL_EVENTS", str(state_dir() / "events.ndjson")))
38+
39+
40+
def response(kind: str, data: dict[str, Any]) -> dict[str, Any]:
41+
return {"schema": SCHEMA, "kind": kind, "capturedAt": now(), "data": data}
42+
43+
44+
def print_json(payload: dict[str, Any]) -> int:
45+
print(json.dumps(payload, indent=2, sort_keys=True))
46+
return 0
47+
48+
49+
def profiles() -> dict[str, Any]:
50+
workspace = env("SOURCEOS_WORKSPACE", "default")
51+
cwd = os.getcwd()
52+
return {
53+
"profiles": [
54+
{"id": "local/default", "label": "Local Workspace", "kind": "host", "workspace_id": workspace, "cwd": cwd, "policy_profile": "standard"},
55+
{"id": "tmux/attached", "label": "Attached tmux Session", "kind": "tmux", "workspace_id": workspace, "cwd": cwd, "policy_profile": "standard"},
56+
{"id": "neovim/context", "label": "Neovim Context", "kind": "editor-context", "workspace_id": workspace, "cwd": cwd, "policy_profile": "review-only"},
57+
{"id": "cloudfog/local-devshell", "label": "CloudFog Local Devshell", "kind": "devshell", "workspace_id": workspace, "cwd": cwd, "policy_profile": "low-risk"},
58+
{"id": "agent-machine/local-agentpod", "label": "Agent Machine Local AgentPod", "kind": "agentpod", "workspace_id": workspace, "cwd": cwd, "policy_profile": "activation-required"}
59+
]
60+
}
61+
62+
63+
def layout_export() -> dict[str, Any]:
64+
return {
65+
"layout": {
66+
"id": f"urn:srcos:turtle-layout:{uuid.uuid4().hex}",
67+
"workspace_id": env("SOURCEOS_WORKSPACE", "default"),
68+
"tabs": [
69+
{"title": "main", "profile": "local/default", "cwd": os.getcwd(), "panes": [{"role": "shell", "profile": "local/default"}]}
70+
],
71+
"mutationAllowed": False,
72+
}
73+
}
74+
75+
76+
def append_mark(text: str) -> dict[str, Any]:
77+
path = marks_path()
78+
path.parent.mkdir(parents=True, exist_ok=True)
79+
mark = {
80+
"id": f"urn:srcos:turtle-mark:{uuid.uuid4().hex}",
81+
"createdAt": now(),
82+
"workspace_id": env("SOURCEOS_WORKSPACE", "default"),
83+
"session_id": env("SOURCEOS_TERMINAL_SESSION_ID", ""),
84+
"text": text,
85+
}
86+
with path.open("a", encoding="utf-8") as handle:
87+
handle.write(json.dumps(mark, sort_keys=True) + "\n")
88+
return {"mark": mark, "path": str(path)}
89+
90+
91+
def read_marks() -> dict[str, Any]:
92+
path = marks_path()
93+
rows = []
94+
if path.exists():
95+
for line in path.read_text(encoding="utf-8").splitlines()[-200:]:
96+
if line.strip():
97+
rows.append(json.loads(line))
98+
return {"marks": rows, "path": str(path)}
99+
100+
101+
def search_events(query: str) -> dict[str, Any]:
102+
results: list[dict[str, Any]] = []
103+
path = events_path()
104+
if path.exists():
105+
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
106+
if query.lower() in line.lower():
107+
try:
108+
results.append(json.loads(line))
109+
except json.JSONDecodeError:
110+
results.append({"raw": line})
111+
marks = [mark for mark in read_marks()["marks"] if query.lower() in json.dumps(mark).lower()]
112+
return {"query": query, "event_results": results[-100:], "mark_results": marks[-100:], "mutationAllowed": False}
113+
114+
115+
def replay_plan() -> dict[str, Any]:
116+
return {
117+
"replay_plan": {
118+
"id": f"urn:srcos:turtle-replay-plan:{uuid.uuid4().hex}",
119+
"workspace_id": env("SOURCEOS_WORKSPACE", "default"),
120+
"session_id": env("SOURCEOS_TERMINAL_SESSION_ID", ""),
121+
"status": "review-required",
122+
"reason": "TurtleTerm v0 emits replay plans only; it does not rerun shell history.",
123+
"mutationAllowed": False,
124+
}
125+
}
126+
127+
128+
def main(argv: list[str]) -> int:
129+
parser = argparse.ArgumentParser(description="TurtleTerm session ergonomics CLI v0")
130+
sub = parser.add_subparsers(dest="command")
131+
sub.add_parser("profiles")
132+
sub.add_parser("layout-export")
133+
sub.add_parser("marks")
134+
mark_p = sub.add_parser("mark")
135+
mark_p.add_argument("text", nargs=argparse.REMAINDER)
136+
search_p = sub.add_parser("search")
137+
search_p.add_argument("query")
138+
sub.add_parser("replay-plan")
139+
args = parser.parse_args(argv)
140+
141+
if args.command == "profiles":
142+
return print_json(response("profiles", profiles()))
143+
if args.command == "layout-export":
144+
return print_json(response("layout", layout_export()))
145+
if args.command == "marks":
146+
return print_json(response("marks", read_marks()))
147+
if args.command == "mark":
148+
text = " ".join(args.text).strip()
149+
if not text:
150+
text = "operator mark"
151+
return print_json(response("mark", append_mark(text)))
152+
if args.command == "search":
153+
return print_json(response("search", search_events(args.query)))
154+
if args.command == "replay-plan":
155+
return print_json(response("replay_plan", replay_plan()))
156+
parser.print_help(sys.stderr)
157+
return 2
158+
159+
160+
if __name__ == "__main__":
161+
raise SystemExit(main(sys.argv[1:]))

0 commit comments

Comments
 (0)