interlatch 0.22.0__py3-none-any.whl
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.
- chronicle/__init__.py +17 -0
- chronicle/__main__.py +3 -0
- chronicle/access.py +93 -0
- chronicle/agents.py +23 -0
- chronicle/analyze.py +498 -0
- chronicle/antigravity_parser.py +311 -0
- chronicle/artifacts.py +621 -0
- chronicle/bob_parser.py +259 -0
- chronicle/cases.py +75 -0
- chronicle/chat_import.py +325 -0
- chronicle/chatgpt_export.py +127 -0
- chronicle/claude_export.py +129 -0
- chronicle/cli.py +2980 -0
- chronicle/codex_cloud.py +138 -0
- chronicle/codex_parser.py +753 -0
- chronicle/config.py +627 -0
- chronicle/connectors.py +666 -0
- chronicle/container.py +202 -0
- chronicle/copilot_parser.py +494 -0
- chronicle/db.py +598 -0
- chronicle/desktop.py +543 -0
- chronicle/diagram.py +488 -0
- chronicle/digest.py +194 -0
- chronicle/export_md.py +210 -0
- chronicle/friction.py +841 -0
- chronicle/glossary.py +522 -0
- chronicle/groups.py +181 -0
- chronicle/hooks.py +169 -0
- chronicle/hub.py +2291 -0
- chronicle/i18n.py +37 -0
- chronicle/i18n_ja.py +421 -0
- chronicle/ingest.py +1190 -0
- chronicle/install.py +590 -0
- chronicle/instructions.py +699 -0
- chronicle/ladder.py +244 -0
- chronicle/learning.py +170 -0
- chronicle/llm.py +570 -0
- chronicle/mcp_server.py +403 -0
- chronicle/menubar.py +771 -0
- chronicle/migrate.py +580 -0
- chronicle/mirror.py +453 -0
- chronicle/news.py +110 -0
- chronicle/notify.py +92 -0
- chronicle/parser.py +908 -0
- chronicle/people.py +536 -0
- chronicle/pg.py +122 -0
- chronicle/pricing.py +168 -0
- chronicle/providers.py +591 -0
- chronicle/redact.py +50 -0
- chronicle/reviews.py +251 -0
- chronicle/screen.py +308 -0
- chronicle/search.py +238 -0
- chronicle/server.py +3009 -0
- chronicle/session_export.py +178 -0
- chronicle/statusline.py +258 -0
- chronicle/suggest.py +643 -0
- chronicle/synthesize.py +365 -0
- chronicle/systems.py +1265 -0
- chronicle/tailnet.py +97 -0
- chronicle/team_store.py +450 -0
- chronicle/update.py +404 -0
- chronicle/util.py +217 -0
- chronicle/views.py +249 -0
- chronicle/web/PLEX-OFL.txt +93 -0
- chronicle/web/app.css +2350 -0
- chronicle/web/app.js +7587 -0
- chronicle/web/art-book.webp +0 -0
- chronicle/web/art-camera.webp +0 -0
- chronicle/web/art-chip.webp +0 -0
- chronicle/web/art-clock.webp +0 -0
- chronicle/web/art-cloud.webp +0 -0
- chronicle/web/art-coins.webp +0 -0
- chronicle/web/art-container.webp +0 -0
- chronicle/web/art-dashboard.webp +0 -0
- chronicle/web/art-dex.webp +0 -0
- chronicle/web/art-document.webp +0 -0
- chronicle/web/art-flag.webp +0 -0
- chronicle/web/art-folder.webp +0 -0
- chronicle/web/art-gateway.webp +0 -0
- chronicle/web/art-gears.webp +0 -0
- chronicle/web/art-glitch.webp +0 -0
- chronicle/web/art-globe.webp +0 -0
- chronicle/web/art-laptop.webp +0 -0
- chronicle/web/art-log.webp +0 -0
- chronicle/web/art-lumi.webp +0 -0
- chronicle/web/art-magnifier.webp +0 -0
- chronicle/web/art-pip.webp +0 -0
- chronicle/web/art-plug.webp +0 -0
- chronicle/web/art-rack.webp +0 -0
- chronicle/web/art-tink.webp +0 -0
- chronicle/web/boot.js +25 -0
- chronicle/web/favicon.png +0 -0
- chronicle/web/i18n.js +50 -0
- chronicle/web/icon-180.png +0 -0
- chronicle/web/icon-192.png +0 -0
- chronicle/web/icon-512.png +0 -0
- chronicle/web/icon.png +0 -0
- chronicle/web/index.html +92 -0
- chronicle/web/ja.js +1880 -0
- chronicle/web/learning.js +438 -0
- chronicle/web/manifest.webmanifest +15 -0
- chronicle/web/panel.css +175 -0
- chronicle/web/panel.html +14 -0
- chronicle/web/panel.js +241 -0
- chronicle/web/plex-mono-400.woff2 +0 -0
- chronicle/web/plex-mono-500.woff2 +0 -0
- chronicle/web/plex-mono-600.woff2 +0 -0
- chronicle/web/plex-sans-400.woff2 +0 -0
- chronicle/web/plex-sans-500.woff2 +0 -0
- chronicle/web/plex-sans-600.woff2 +0 -0
- chronicle/web/plex-sans-700.woff2 +0 -0
- chronicle/web/rough.js +2 -0
- chronicle/worker.py +390 -0
- interlatch-0.22.0.dist-info/METADATA +221 -0
- interlatch-0.22.0.dist-info/RECORD +118 -0
- interlatch-0.22.0.dist-info/WHEEL +4 -0
- interlatch-0.22.0.dist-info/entry_points.txt +3 -0
- interlatch-0.22.0.dist-info/licenses/LICENSE +21 -0
chronicle/codex_cloud.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Codex Cloud tasks (chatgpt.com/codex), read through the codex CLI with the user's own Codex login.
|
|
2
|
+
|
|
3
|
+
`codex cloud list --json` gives each task's title, environment (the repository), status, change counts and last
|
|
4
|
+
update; `codex cloud diff <id>` gives its diff. The CLI does not give a task's conversation, so a cloud task becomes
|
|
5
|
+
a session without prompts: the title, the files it changed and the diff, with a link to the task. Interlatch never
|
|
6
|
+
reads Codex's login state itself; the CLI does the talking.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import logging
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
from .copilot_parser import _Builder, finish_session
|
|
18
|
+
from .hooks import internal_env
|
|
19
|
+
from .parser import FileStat, ParsedSession
|
|
20
|
+
from .util import safe_text
|
|
21
|
+
|
|
22
|
+
log = logging.getLogger("chronicle.codex_cloud")
|
|
23
|
+
|
|
24
|
+
CLOUD_PARSER_VERSION = 1
|
|
25
|
+
STATUS_KEY = "codex-cloud:status" # kv: the last listing's outcome, for the Sources page
|
|
26
|
+
MAX_PAGES = 25 # 20 tasks a page
|
|
27
|
+
_DIFF_FILE_RE = re.compile(r"^diff --git a/(.+?) b/(.+)$")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CloudError(Exception):
|
|
31
|
+
"""The codex CLI could not list tasks (not logged in, offline, an old CLI without `codex cloud`)."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _run(binary: str, *args: str, timeout: float = 120) -> str:
|
|
35
|
+
try:
|
|
36
|
+
proc = subprocess.run([binary, "cloud", *args], capture_output=True, text=True, timeout=timeout,
|
|
37
|
+
stdin=subprocess.DEVNULL, env=internal_env())
|
|
38
|
+
except subprocess.TimeoutExpired as exc:
|
|
39
|
+
raise CloudError(f"codex cloud {args[0]} timed out") from exc
|
|
40
|
+
except OSError as exc:
|
|
41
|
+
raise CloudError(f"could not run codex: {exc}") from exc
|
|
42
|
+
if proc.returncode:
|
|
43
|
+
msg = (proc.stderr or proc.stdout).strip().splitlines()
|
|
44
|
+
raise CloudError(msg[-1][:300] if msg else f"codex cloud {args[0]} exited with {proc.returncode}")
|
|
45
|
+
return proc.stdout
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def list_tasks(binary: str) -> list[dict]:
|
|
49
|
+
"""Every task the account can see, newest first."""
|
|
50
|
+
tasks, cursor = [], None
|
|
51
|
+
for _ in range(MAX_PAGES):
|
|
52
|
+
out = _run(binary, "list", "--json", "--limit", "20", *(["--cursor", cursor] if cursor else []))
|
|
53
|
+
try:
|
|
54
|
+
page = json.loads(out)
|
|
55
|
+
except ValueError as exc:
|
|
56
|
+
raise CloudError("codex cloud list did not return JSON (update the Codex CLI)") from exc
|
|
57
|
+
tasks += [t for t in page.get("tasks") or [] if isinstance(t, dict) and t.get("id")]
|
|
58
|
+
cursor = page.get("cursor")
|
|
59
|
+
if not cursor:
|
|
60
|
+
break
|
|
61
|
+
return tasks
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def task_diff(binary: str, task_id: str) -> str | None:
|
|
65
|
+
"""The task's diff, or None when it has none yet (still running) or the CLI fails."""
|
|
66
|
+
try:
|
|
67
|
+
return _run(binary, "diff", task_id) or None
|
|
68
|
+
except CloudError as exc:
|
|
69
|
+
log.info("no diff for %s: %s", task_id, exc)
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def diff_files(diff: str | None) -> dict[str, tuple[int, int]]:
|
|
74
|
+
"""Lines added and removed per file of a unified git diff."""
|
|
75
|
+
files: dict[str, list[int]] = {}
|
|
76
|
+
cur = None
|
|
77
|
+
for line in (diff or "").splitlines():
|
|
78
|
+
m = _DIFF_FILE_RE.match(line)
|
|
79
|
+
if m:
|
|
80
|
+
cur = files.setdefault(m.group(2), [0, 0])
|
|
81
|
+
elif cur is None or line.startswith(("+++", "---")):
|
|
82
|
+
continue
|
|
83
|
+
elif line.startswith("+"):
|
|
84
|
+
cur[0] += 1
|
|
85
|
+
elif line.startswith("-"):
|
|
86
|
+
cur[1] += 1
|
|
87
|
+
return {p: (a, r) for p, (a, r) in files.items()}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _ms(ts: str | None) -> str | None:
|
|
91
|
+
"""Codex Cloud times have nanoseconds; the rest of Interlatch stores milliseconds ("…:28.652Z")."""
|
|
92
|
+
return re.sub(r"(\.\d{3})\d+", r"\1", ts) if ts else None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def task_signature(task: dict) -> str:
|
|
96
|
+
return f"{task.get('updated_at')}|{task.get('status')}|{task.get('attempt_total')}|v{CLOUD_PARSER_VERSION}"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def parse_cloud_task(task: dict, diff: str | None, project_path: str | None) -> ParsedSession:
|
|
100
|
+
ps = ParsedSession(id=task["id"])
|
|
101
|
+
ps.project_path = project_path
|
|
102
|
+
ps.entrypoint = "codex:cloud"
|
|
103
|
+
ps.custom_title = safe_text(task.get("title") or "") or None
|
|
104
|
+
ps.artifacts.append({"title": "Open in Codex Cloud", "url": task.get("url"), "ts": task.get("updated_at")})
|
|
105
|
+
ts = _ms(task.get("updated_at"))
|
|
106
|
+
b = _Builder(ps)
|
|
107
|
+
env = task.get("environment_label") or task.get("environment_id") or "no environment"
|
|
108
|
+
s = task.get("summary") or {}
|
|
109
|
+
head = f"Codex Cloud task · {env} · {task.get('status') or 'unknown'}"
|
|
110
|
+
if s.get("files_changed"):
|
|
111
|
+
head += f" · +{s.get('lines_added', 0)} −{s.get('lines_removed', 0)} in {s['files_changed']} files"
|
|
112
|
+
if (task.get("attempt_total") or 1) > 1:
|
|
113
|
+
head += f" · {task['attempt_total']} attempts"
|
|
114
|
+
b.event(ts, "system", "meta", head)
|
|
115
|
+
files = diff_files(diff)
|
|
116
|
+
for path, (added, removed) in files.items():
|
|
117
|
+
ps.files[path] = FileStat(edits=1, lines_added=added, lines_removed=removed)
|
|
118
|
+
if files:
|
|
119
|
+
listing = "\n".join(f"- {p} (+{a} −{r})" for p, (a, r) in sorted(files.items()))
|
|
120
|
+
b.event(ts, "assistant", "text", f"Changed {len(files)} file{'s' if len(files) != 1 else ''}:\n{listing}")
|
|
121
|
+
b.event(ts, "assistant", "text", f"```diff\n{diff.rstrip()}\n```")
|
|
122
|
+
b.event(ts, "system", "meta", "Codex's CLI does not share a cloud task's conversation; open the task for it.")
|
|
123
|
+
return finish_session(ps, [ts] if ts else [])
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def save_status(conn, **status) -> None:
|
|
127
|
+
from .db import kv_set
|
|
128
|
+
|
|
129
|
+
kv_set(conn, STATUS_KEY, json.dumps({**status, "checked_at": time.time()}))
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def load_status(conn) -> dict:
|
|
133
|
+
from .db import kv_get
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
return json.loads(kv_get(conn, STATUS_KEY) or "{}")
|
|
137
|
+
except ValueError:
|
|
138
|
+
return {}
|