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.
Files changed (118) hide show
  1. chronicle/__init__.py +17 -0
  2. chronicle/__main__.py +3 -0
  3. chronicle/access.py +93 -0
  4. chronicle/agents.py +23 -0
  5. chronicle/analyze.py +498 -0
  6. chronicle/antigravity_parser.py +311 -0
  7. chronicle/artifacts.py +621 -0
  8. chronicle/bob_parser.py +259 -0
  9. chronicle/cases.py +75 -0
  10. chronicle/chat_import.py +325 -0
  11. chronicle/chatgpt_export.py +127 -0
  12. chronicle/claude_export.py +129 -0
  13. chronicle/cli.py +2980 -0
  14. chronicle/codex_cloud.py +138 -0
  15. chronicle/codex_parser.py +753 -0
  16. chronicle/config.py +627 -0
  17. chronicle/connectors.py +666 -0
  18. chronicle/container.py +202 -0
  19. chronicle/copilot_parser.py +494 -0
  20. chronicle/db.py +598 -0
  21. chronicle/desktop.py +543 -0
  22. chronicle/diagram.py +488 -0
  23. chronicle/digest.py +194 -0
  24. chronicle/export_md.py +210 -0
  25. chronicle/friction.py +841 -0
  26. chronicle/glossary.py +522 -0
  27. chronicle/groups.py +181 -0
  28. chronicle/hooks.py +169 -0
  29. chronicle/hub.py +2291 -0
  30. chronicle/i18n.py +37 -0
  31. chronicle/i18n_ja.py +421 -0
  32. chronicle/ingest.py +1190 -0
  33. chronicle/install.py +590 -0
  34. chronicle/instructions.py +699 -0
  35. chronicle/ladder.py +244 -0
  36. chronicle/learning.py +170 -0
  37. chronicle/llm.py +570 -0
  38. chronicle/mcp_server.py +403 -0
  39. chronicle/menubar.py +771 -0
  40. chronicle/migrate.py +580 -0
  41. chronicle/mirror.py +453 -0
  42. chronicle/news.py +110 -0
  43. chronicle/notify.py +92 -0
  44. chronicle/parser.py +908 -0
  45. chronicle/people.py +536 -0
  46. chronicle/pg.py +122 -0
  47. chronicle/pricing.py +168 -0
  48. chronicle/providers.py +591 -0
  49. chronicle/redact.py +50 -0
  50. chronicle/reviews.py +251 -0
  51. chronicle/screen.py +308 -0
  52. chronicle/search.py +238 -0
  53. chronicle/server.py +3009 -0
  54. chronicle/session_export.py +178 -0
  55. chronicle/statusline.py +258 -0
  56. chronicle/suggest.py +643 -0
  57. chronicle/synthesize.py +365 -0
  58. chronicle/systems.py +1265 -0
  59. chronicle/tailnet.py +97 -0
  60. chronicle/team_store.py +450 -0
  61. chronicle/update.py +404 -0
  62. chronicle/util.py +217 -0
  63. chronicle/views.py +249 -0
  64. chronicle/web/PLEX-OFL.txt +93 -0
  65. chronicle/web/app.css +2350 -0
  66. chronicle/web/app.js +7587 -0
  67. chronicle/web/art-book.webp +0 -0
  68. chronicle/web/art-camera.webp +0 -0
  69. chronicle/web/art-chip.webp +0 -0
  70. chronicle/web/art-clock.webp +0 -0
  71. chronicle/web/art-cloud.webp +0 -0
  72. chronicle/web/art-coins.webp +0 -0
  73. chronicle/web/art-container.webp +0 -0
  74. chronicle/web/art-dashboard.webp +0 -0
  75. chronicle/web/art-dex.webp +0 -0
  76. chronicle/web/art-document.webp +0 -0
  77. chronicle/web/art-flag.webp +0 -0
  78. chronicle/web/art-folder.webp +0 -0
  79. chronicle/web/art-gateway.webp +0 -0
  80. chronicle/web/art-gears.webp +0 -0
  81. chronicle/web/art-glitch.webp +0 -0
  82. chronicle/web/art-globe.webp +0 -0
  83. chronicle/web/art-laptop.webp +0 -0
  84. chronicle/web/art-log.webp +0 -0
  85. chronicle/web/art-lumi.webp +0 -0
  86. chronicle/web/art-magnifier.webp +0 -0
  87. chronicle/web/art-pip.webp +0 -0
  88. chronicle/web/art-plug.webp +0 -0
  89. chronicle/web/art-rack.webp +0 -0
  90. chronicle/web/art-tink.webp +0 -0
  91. chronicle/web/boot.js +25 -0
  92. chronicle/web/favicon.png +0 -0
  93. chronicle/web/i18n.js +50 -0
  94. chronicle/web/icon-180.png +0 -0
  95. chronicle/web/icon-192.png +0 -0
  96. chronicle/web/icon-512.png +0 -0
  97. chronicle/web/icon.png +0 -0
  98. chronicle/web/index.html +92 -0
  99. chronicle/web/ja.js +1880 -0
  100. chronicle/web/learning.js +438 -0
  101. chronicle/web/manifest.webmanifest +15 -0
  102. chronicle/web/panel.css +175 -0
  103. chronicle/web/panel.html +14 -0
  104. chronicle/web/panel.js +241 -0
  105. chronicle/web/plex-mono-400.woff2 +0 -0
  106. chronicle/web/plex-mono-500.woff2 +0 -0
  107. chronicle/web/plex-mono-600.woff2 +0 -0
  108. chronicle/web/plex-sans-400.woff2 +0 -0
  109. chronicle/web/plex-sans-500.woff2 +0 -0
  110. chronicle/web/plex-sans-600.woff2 +0 -0
  111. chronicle/web/plex-sans-700.woff2 +0 -0
  112. chronicle/web/rough.js +2 -0
  113. chronicle/worker.py +390 -0
  114. interlatch-0.22.0.dist-info/METADATA +221 -0
  115. interlatch-0.22.0.dist-info/RECORD +118 -0
  116. interlatch-0.22.0.dist-info/WHEEL +4 -0
  117. interlatch-0.22.0.dist-info/entry_points.txt +3 -0
  118. interlatch-0.22.0.dist-info/licenses/LICENSE +21 -0
chronicle/pricing.py ADDED
@@ -0,0 +1,168 @@
1
+ """API-equivalent cost estimation from transcript usage blocks.
2
+
3
+ Prices are USD per million tokens (first-party list prices). Cache writes cost 1.25x input for
4
+ the 5-minute TTL and 2x for the 1-hour TTL; cache reads are 0.1x input except where noted.
5
+ Subscription users do not pay these amounts; they are a consistent yardstick for usage.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+
12
+ # model id -> (input, output, cache_read)
13
+ PRICES: dict[str, tuple[float, float, float]] = {
14
+ "claude-fable-5-1": (10.0, 50.0, 0.25),
15
+ "claude-mythos-5-1": (10.0, 50.0, 0.25),
16
+ "claude-fable-5": (10.0, 50.0, 1.0),
17
+ "claude-mythos-5": (10.0, 50.0, 1.0),
18
+ "claude-mythos-preview": (10.0, 50.0, 1.0),
19
+ "claude-opus-5-5": (4.0, 20.0, 0.20),
20
+ "claude-opus-5": (5.0, 25.0, 0.50),
21
+ "claude-opus-4-8": (5.0, 25.0, 0.50),
22
+ "claude-opus-4-7": (5.0, 25.0, 0.50),
23
+ "claude-opus-4-6": (5.0, 25.0, 0.50),
24
+ "claude-opus-4-5": (5.0, 25.0, 0.50),
25
+ "claude-opus-4-1": (15.0, 75.0, 1.50),
26
+ "claude-opus-4-0": (15.0, 75.0, 1.50),
27
+ "claude-opus-4": (15.0, 75.0, 1.50),
28
+ "claude-sonnet-5": (2.0, 10.0, 0.20),
29
+ "claude-sonnet-4-6": (3.0, 15.0, 0.30),
30
+ "claude-sonnet-4-5": (3.0, 15.0, 0.30),
31
+ "claude-sonnet-4-0": (3.0, 15.0, 0.30),
32
+ "claude-sonnet-4": (3.0, 15.0, 0.30),
33
+ "claude-3-7-sonnet": (3.0, 15.0, 0.30),
34
+ "claude-3-5-sonnet": (3.0, 15.0, 0.30),
35
+ "claude-haiku-4-5": (1.0, 5.0, 0.10),
36
+ "claude-3-5-haiku": (0.80, 4.0, 0.08),
37
+ "claude-3-haiku": (0.25, 1.25, 0.03),
38
+ "claude-3-opus": (15.0, 75.0, 1.50),
39
+ }
40
+
41
+ # Fallback by family when an unknown (newer) model shows up.
42
+ FAMILY_FALLBACK = {
43
+ "fable": "claude-fable-5-1",
44
+ "mythos": "claude-mythos-5-1",
45
+ "opus": "claude-opus-5",
46
+ "sonnet": "claude-sonnet-5",
47
+ "haiku": "claude-haiku-4-5",
48
+ }
49
+
50
+ WEB_SEARCH_PER_REQUEST = 10.0 / 1000 # $10 per 1,000 searches
51
+ FAST_MODE_MULTIPLIER = 2.0
52
+
53
+ _DATE_SUFFIX = re.compile(r"-\d{8}$")
54
+
55
+
56
+ def normalize_model(model: str | None) -> str | None:
57
+ """'claude-opus-5-5[1m]' -> 'claude-opus-5-5'; 'claude-haiku-4-5-20251001' -> 'claude-haiku-4-5'."""
58
+ if not model or model.startswith("<"):
59
+ return None
60
+ m = model.strip().lower()
61
+ m = re.sub(r"\[.*?\]$", "", m)
62
+ m = m.split("@")[0]
63
+ m = re.sub(r"^(us|eu|apac|global)\.anthropic\.", "", m)
64
+ m = re.sub(r"^anthropic\.", "", m)
65
+ m = re.sub(r"-v\d+(:\d+)?$", "", m)
66
+ m = _DATE_SUFFIX.sub("", m)
67
+ return m
68
+
69
+
70
+ def price_for(model: str | None) -> tuple[tuple[float, float, float] | None, bool]:
71
+ """Return ((input, output, cache_read), exact_match)."""
72
+ m = normalize_model(model)
73
+ if not m:
74
+ return None, True
75
+ if m in PRICES:
76
+ return PRICES[m], True
77
+ # longest known prefix (e.g. a dated or suffixed variant)
78
+ for known in sorted(PRICES, key=len, reverse=True):
79
+ if m.startswith(known):
80
+ return PRICES[known], True
81
+ for family, fallback in FAMILY_FALLBACK.items():
82
+ if family in m:
83
+ return PRICES[fallback], False
84
+ return None, False
85
+
86
+
87
+ def usage_cost(model: str | None, usage: dict | None, speed: str | None = None) -> float:
88
+ """Estimate the USD cost of one API response from its usage block."""
89
+ if not usage:
90
+ return 0.0
91
+ prices, _ = price_for(model)
92
+ if not prices:
93
+ return 0.0
94
+ p_in, p_out, p_cache_read = prices
95
+ input_tokens = usage.get("input_tokens") or 0
96
+ output_tokens = usage.get("output_tokens") or 0
97
+ cache_read = usage.get("cache_read_input_tokens") or 0
98
+ cache_creation = usage.get("cache_creation_input_tokens") or 0
99
+ breakdown = usage.get("cache_creation") or {}
100
+ write_1h = breakdown.get("ephemeral_1h_input_tokens") or 0
101
+ write_5m = breakdown.get("ephemeral_5m_input_tokens")
102
+ if write_5m is None:
103
+ write_5m = max(cache_creation - write_1h, 0)
104
+ cost = (
105
+ input_tokens * p_in
106
+ + write_5m * p_in * 1.25
107
+ + write_1h * p_in * 2.0
108
+ + cache_read * p_cache_read
109
+ + output_tokens * p_out
110
+ ) / 1_000_000
111
+ if (speed or usage.get("speed")) == "fast":
112
+ cost *= FAST_MODE_MULTIPLIER
113
+ server = usage.get("server_tool_use") or {}
114
+ cost += (server.get("web_search_requests") or 0) * WEB_SEARCH_PER_REQUEST
115
+ return cost
116
+
117
+
118
+ # ------------------------------------------------------------------ OpenAI (Codex sessions)
119
+ # model -> (input, output, cached input) USD per million tokens; reasoning tokens bill as output.
120
+ OPENAI_PRICES: dict[str, tuple[float, float, float]] = {
121
+ "gpt-5": (1.25, 10.0, 0.125),
122
+ "gpt-5-codex": (1.25, 10.0, 0.125),
123
+ "gpt-5-mini": (0.25, 2.0, 0.025),
124
+ "gpt-5-nano": (0.05, 0.40, 0.005),
125
+ "gpt-4.1": (2.0, 8.0, 0.5),
126
+ "gpt-4.1-mini": (0.4, 1.6, 0.1),
127
+ "gpt-4.1-nano": (0.1, 0.4, 0.025),
128
+ "gpt-4o": (2.5, 10.0, 1.25),
129
+ "gpt-4o-mini": (0.15, 0.6, 0.075),
130
+ "o3": (2.0, 8.0, 0.5),
131
+ "o3-pro": (20.0, 80.0, 20.0),
132
+ "o4-mini": (1.1, 4.4, 0.275),
133
+ "codex-mini-latest": (1.5, 6.0, 0.375),
134
+ }
135
+
136
+
137
+ def is_openai_model(model: str | None) -> bool:
138
+ m = normalize_model(model) or ""
139
+ return m.startswith(("gpt-", "o1", "o3", "o4", "codex-"))
140
+
141
+
142
+ def openai_price_for(model: str | None) -> tuple[tuple[float, float, float] | None, bool]:
143
+ """((input, output, cached), exact). Newer GPT-5.x/6 variants fall back to GPT-5 rates as an estimate."""
144
+ m = normalize_model(model)
145
+ if not m:
146
+ return None, True
147
+ if m in OPENAI_PRICES:
148
+ return OPENAI_PRICES[m], True
149
+ for known in sorted(OPENAI_PRICES, key=len, reverse=True):
150
+ if m.startswith(known + "-") or m.startswith(known + "."):
151
+ return OPENAI_PRICES[known], True
152
+ if "nano" in m:
153
+ return OPENAI_PRICES["gpt-5-nano"], False
154
+ if "mini" in m:
155
+ return OPENAI_PRICES["gpt-5-mini"], False
156
+ if m.startswith(("gpt-5", "gpt-6", "gpt-", "codex")):
157
+ return OPENAI_PRICES["gpt-5"], False
158
+ if m.startswith(("o1", "o3", "o4")):
159
+ return OPENAI_PRICES["o3"], False
160
+ return None, False
161
+
162
+
163
+ def openai_cost(model: str | None, uncached_input: int, cached_input: int, output: int) -> float:
164
+ prices, _ = openai_price_for(model)
165
+ if not prices:
166
+ return 0.0
167
+ p_in, p_out, p_cached = prices
168
+ return (uncached_input * p_in + cached_input * p_cached + output * p_out) / 1_000_000