codex-flow 2.1.13__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 (113) hide show
  1. codex_flow/__init__.py +28 -0
  2. codex_flow/__main__.py +9 -0
  3. codex_flow/cli.py +242 -0
  4. codex_flow/data/LICENSE +21 -0
  5. codex_flow/data/README.en.md +303 -0
  6. codex_flow/data/README.md +305 -0
  7. codex_flow/data/VERSION +1 -0
  8. codex_flow/data/apps/chatgpt-mcp/README.md +86 -0
  9. codex_flow/data/apps/chatgpt-mcp/__init__.py +1 -0
  10. codex_flow/data/apps/chatgpt-mcp/adapter.py +458 -0
  11. codex_flow/data/apps/chatgpt-mcp/server.py +358 -0
  12. codex_flow/data/apps/chatgpt-mcp/widget.html +927 -0
  13. codex_flow/data/apps/macos-overlay/README.en.md +121 -0
  14. codex_flow/data/apps/macos-overlay/README.md +123 -0
  15. codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayRuntimeState.swift +126 -0
  16. codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayScreenGeometry.swift +82 -0
  17. codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayWindowController.swift +1052 -0
  18. codex_flow/data/apps/macos-overlay/Sources/Localization.swift +197 -0
  19. codex_flow/data/apps/macos-overlay/Sources/Models/TelemetryData.swift +1557 -0
  20. codex_flow/data/apps/macos-overlay/Sources/Services/AccountSnapshotService.swift +1101 -0
  21. codex_flow/data/apps/macos-overlay/Sources/Services/FlowPilotInstanceLock.swift +153 -0
  22. codex_flow/data/apps/macos-overlay/Sources/Services/IPCServer.swift +298 -0
  23. codex_flow/data/apps/macos-overlay/Sources/Services/TelemetryQueryEngine.swift +800 -0
  24. codex_flow/data/apps/macos-overlay/Sources/Services/TelemetryWatcher.swift +135 -0
  25. codex_flow/data/apps/macos-overlay/Sources/Services/UpdateService.swift +610 -0
  26. codex_flow/data/apps/macos-overlay/Sources/Views/AccountView.swift +610 -0
  27. codex_flow/data/apps/macos-overlay/Sources/Views/AnalyticsView.swift +566 -0
  28. codex_flow/data/apps/macos-overlay/Sources/Views/AutostartView.swift +293 -0
  29. codex_flow/data/apps/macos-overlay/Sources/Views/BubbleView.swift +317 -0
  30. codex_flow/data/apps/macos-overlay/Sources/Views/HistoryView.swift +1124 -0
  31. codex_flow/data/apps/macos-overlay/Sources/Views/HoverRevealText.swift +165 -0
  32. codex_flow/data/apps/macos-overlay/Sources/Views/InspectorSkillsToolsView.swift +121 -0
  33. codex_flow/data/apps/macos-overlay/Sources/Views/LogoView.swift +182 -0
  34. codex_flow/data/apps/macos-overlay/Sources/Views/SleekSwitch.swift +117 -0
  35. codex_flow/data/apps/macos-overlay/Sources/Views/StrategyModeView.swift +561 -0
  36. codex_flow/data/apps/macos-overlay/Sources/Views/SummaryView.swift +1273 -0
  37. codex_flow/data/apps/macos-overlay/Sources/Views/UpdateView.swift +352 -0
  38. codex_flow/data/apps/macos-overlay/Sources/main.swift +340 -0
  39. codex_flow/data/apps/macos-overlay/Tests/OverlayScreenGeometryTests.swift +163 -0
  40. codex_flow/data/apps/macos-overlay/Tests/TelemetryPhase1ContractTests.swift +357 -0
  41. codex_flow/data/apps/macos-overlay/Tests/TelemetryQueryEngineConcurrencyTests.swift +221 -0
  42. codex_flow/data/apps/macos-overlay/Tests/TelemetryQuotaSelectionTests.swift +158 -0
  43. codex_flow/data/apps/macos-overlay/Tests/TelemetryWorkerTokenTests.swift +122 -0
  44. codex_flow/data/apps/macos-overlay/build.sh +75 -0
  45. codex_flow/data/benchmark/corpus.json +103 -0
  46. codex_flow/data/benchmark/manifest.example.json +41 -0
  47. codex_flow/data/benchmark/manifest.schema.json +137 -0
  48. codex_flow/data/benchmark/prices/gpt-5.6-2026-08-30.json +5 -0
  49. codex_flow/data/benchmark/profiles.json +90 -0
  50. codex_flow/data/benchmark/schema.json +77 -0
  51. codex_flow/data/benchmark/tasks.json +50 -0
  52. codex_flow/data/completions/codex-flow.bash +34 -0
  53. codex_flow/data/completions/codex-flow.zsh +52 -0
  54. codex_flow/data/glama.json +6 -0
  55. codex_flow/data/install-release.ps1 +126 -0
  56. codex_flow/data/install-release.sh +155 -0
  57. codex_flow/data/install.ps1 +349 -0
  58. codex_flow/data/install.sh +362 -0
  59. codex_flow/data/policy/benchmark.toml +49 -0
  60. codex_flow/data/policy/defaults.toml +70 -0
  61. codex_flow/data/scripts/analyze-benchmark.py +510 -0
  62. codex_flow/data/scripts/benchmark-local.py +171 -0
  63. codex_flow/data/scripts/check-recommendation.py +277 -0
  64. codex_flow/data/scripts/doctor.py +449 -0
  65. codex_flow/data/scripts/generate-release-manifest.py +74 -0
  66. codex_flow/data/scripts/localization.py +192 -0
  67. codex_flow/data/scripts/manage-hooks.py +448 -0
  68. codex_flow/data/scripts/manage-instructions.py +389 -0
  69. codex_flow/data/scripts/manage-shell.py +151 -0
  70. codex_flow/data/scripts/materialize-corpus.py +193 -0
  71. codex_flow/data/scripts/menu.py +646 -0
  72. codex_flow/data/scripts/migrations/0001_update_settings.py +80 -0
  73. codex_flow/data/scripts/package-release.py +132 -0
  74. codex_flow/data/scripts/render-benchmark-report.py +292 -0
  75. codex_flow/data/scripts/run-benchmark.py +829 -0
  76. codex_flow/data/scripts/strategies/__init__.py +28 -0
  77. codex_flow/data/scripts/strategies/balanced.py +115 -0
  78. codex_flow/data/scripts/strategies/base.py +363 -0
  79. codex_flow/data/scripts/strategies/efficient.py +158 -0
  80. codex_flow/data/scripts/strategies/lifecycle_runtime.py +590 -0
  81. codex_flow/data/scripts/strategies/quality.py +209 -0
  82. codex_flow/data/scripts/strategies/speed.py +108 -0
  83. codex_flow/data/scripts/strategies/task_budget_runtime.py +644 -0
  84. codex_flow/data/scripts/strategies/task_phase_runtime.py +341 -0
  85. codex_flow/data/scripts/strategies/work_unit_runtime.py +421 -0
  86. codex_flow/data/scripts/strategy_runtime.py +1091 -0
  87. codex_flow/data/scripts/telemetry.py +400 -0
  88. codex_flow/data/scripts/telemetry_core/__init__.py +192 -0
  89. codex_flow/data/scripts/telemetry_core/app_server.py +1192 -0
  90. codex_flow/data/scripts/telemetry_core/collector.py +1247 -0
  91. codex_flow/data/scripts/telemetry_core/common.py +421 -0
  92. codex_flow/data/scripts/telemetry_core/latency.py +593 -0
  93. codex_flow/data/scripts/telemetry_core/query.py +427 -0
  94. codex_flow/data/scripts/telemetry_core/quota_ledger.py +598 -0
  95. codex_flow/data/scripts/telemetry_core/render.py +460 -0
  96. codex_flow/data/scripts/telemetry_core/repair.py +223 -0
  97. codex_flow/data/scripts/ui.py +266 -0
  98. codex_flow/data/scripts/update-homebrew-formula.py +146 -0
  99. codex_flow/data/scripts/update_runtime_config.py +134 -0
  100. codex_flow/data/scripts/updater.py +1718 -0
  101. codex_flow/data/smithery.yaml +18 -0
  102. codex_flow/data/templates/agents/worker-explorer.toml +24 -0
  103. codex_flow/data/templates/agents/worker-implementer.toml +49 -0
  104. codex_flow/data/templates/agents/worker-reviewer.toml +25 -0
  105. codex_flow/data/templates/flow-pilot-instructions.md +35 -0
  106. codex_flow/data/templates/skills/flow-pilot/SKILL.md +577 -0
  107. codex_flow/mcp.py +35 -0
  108. codex_flow-2.1.13.dist-info/METADATA +342 -0
  109. codex_flow-2.1.13.dist-info/RECORD +113 -0
  110. codex_flow-2.1.13.dist-info/WHEEL +5 -0
  111. codex_flow-2.1.13.dist-info/entry_points.txt +3 -0
  112. codex_flow-2.1.13.dist-info/licenses/LICENSE +21 -0
  113. codex_flow-2.1.13.dist-info/top_level.txt +1 -0
@@ -0,0 +1,358 @@
1
+ """Minimal loopback MCP Streamable HTTP server for FlowPilot telemetry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import ipaddress
7
+ import json
8
+ import signal
9
+ import sys
10
+ import threading
11
+ from http import HTTPStatus
12
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
13
+ from pathlib import Path
14
+ from typing import Any, Dict, List, Optional, Tuple
15
+
16
+
17
+ _MODULE_DIR = Path(__file__).resolve().parent
18
+ if __package__:
19
+ from . import adapter
20
+ else:
21
+ if str(_MODULE_DIR) not in sys.path:
22
+ sys.path.insert(0, str(_MODULE_DIR))
23
+ import adapter # type: ignore
24
+
25
+
26
+ JSONRPC_VERSION = "2.0"
27
+ SERVER_NAME = "chatgpt-codex-flow"
28
+ SERVER_VERSION = "2.1.13"
29
+ MAX_REQUEST_BYTES = 1024 * 1024
30
+
31
+
32
+ def _response(request_id: Any, result: Any = None, error: Any = None) -> Dict[str, Any]:
33
+ payload: Dict[str, Any] = {"jsonrpc": JSONRPC_VERSION, "id": request_id}
34
+ if error is not None:
35
+ payload["error"] = error
36
+ else:
37
+ payload["result"] = result
38
+ return payload
39
+
40
+
41
+ def _error(code: int, message: str) -> Dict[str, Any]:
42
+ return {"code": code, "message": message}
43
+
44
+
45
+ def _valid_id(value: Any) -> bool:
46
+ return value is None or isinstance(value, (str, int, float)) and not isinstance(value, bool)
47
+
48
+
49
+ def _initialize_result(params: Any) -> Dict[str, Any]:
50
+ requested = params.get("protocolVersion") if isinstance(params, dict) else None
51
+ # Echo versions understood by current MCP clients. If a future client
52
+ # version is sent, fall back to this server's known Streamable HTTP version.
53
+ supported = {"2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"}
54
+ protocol_version = requested if requested in supported else "2025-03-26"
55
+ return {
56
+ "protocolVersion": protocol_version,
57
+ "capabilities": {
58
+ "tools": {"listChanged": False},
59
+ "resources": {"subscribe": False, "listChanged": False},
60
+ },
61
+ "serverInfo": {
62
+ "name": SERVER_NAME,
63
+ "title": "FlowPilot telemetry",
64
+ "version": SERVER_VERSION,
65
+ },
66
+ "instructions": "Use flowpilot_get_telemetry for read-only run telemetry.",
67
+ }
68
+
69
+
70
+ def _dispatch(request: Any) -> Optional[Dict[str, Any]]:
71
+ """Dispatch one JSON-RPC value; ``None`` means notification."""
72
+
73
+ if not isinstance(request, dict):
74
+ return _response(None, error=_error(-32600, "Invalid Request"))
75
+ request_id = request.get("id")
76
+ has_id = "id" in request
77
+ if request.get("jsonrpc") != JSONRPC_VERSION:
78
+ return _response(request_id if has_id else None, error=_error(-32600, "Invalid Request"))
79
+ if has_id and not _valid_id(request_id):
80
+ return _response(None, error=_error(-32600, "Invalid Request"))
81
+ method = request.get("method")
82
+ if not isinstance(method, str):
83
+ return _response(request_id if has_id else None, error=_error(-32600, "Invalid Request"))
84
+ params = request.get("params")
85
+
86
+ if method == "notifications/initialized":
87
+ # MCP sends this as a notification (without an id). If a caller
88
+ # incorrectly supplies an id, preserve JSON-RPC request semantics
89
+ # instead of silently dropping that id.
90
+ return None if not has_id else _response(request_id, result={})
91
+ if method == "initialize":
92
+ if params is not None and not isinstance(params, dict):
93
+ result = _response(request_id if has_id else None, error=_error(-32602, "Invalid params"))
94
+ else:
95
+ result = _response(request_id if has_id else None, result=_initialize_result(params or {}))
96
+ elif method == "ping":
97
+ result = _response(request_id if has_id else None, result={})
98
+ elif method == "tools/list":
99
+ result = _response(request_id if has_id else None, result={"tools": [adapter.tool_descriptor()]})
100
+ elif method == "resources/list":
101
+ result = _response(
102
+ request_id if has_id else None,
103
+ result={"resources": [adapter.resource_descriptor()]},
104
+ )
105
+ elif method == "resources/read":
106
+ if not isinstance(params, dict) or not isinstance(params.get("uri"), str):
107
+ result = _response(request_id if has_id else None, error=_error(-32602, "Invalid params"))
108
+ elif params.get("uri") != adapter.RESOURCE_URI:
109
+ result = _response(request_id if has_id else None, error=_error(-32602, "Unknown resource"))
110
+ else:
111
+ try:
112
+ text = adapter.read_widget()
113
+ except adapter.WidgetResourceError:
114
+ result = _response(request_id if has_id else None, error=_error(-32002, "Resource unavailable"))
115
+ else:
116
+ result = _response(
117
+ request_id if has_id else None,
118
+ result={
119
+ "contents": [
120
+ {
121
+ "uri": adapter.RESOURCE_URI,
122
+ "mimeType": adapter.RESOURCE_MIME_TYPE,
123
+ "text": text,
124
+ }
125
+ ]
126
+ },
127
+ )
128
+ elif method == "tools/call":
129
+ if not isinstance(params, dict) or not isinstance(params.get("name"), str):
130
+ result = _response(request_id if has_id else None, error=_error(-32602, "Invalid params"))
131
+ elif params.get("name") != adapter.TOOL_NAME:
132
+ result = _response(request_id if has_id else None, error=_error(-32602, "Unknown tool"))
133
+ else:
134
+ arguments = params.get("arguments")
135
+ if arguments is not None and not isinstance(arguments, dict):
136
+ result = _response(request_id if has_id else None, error=_error(-32602, "Invalid params"))
137
+ else:
138
+ try:
139
+ tool_value = adapter.tool_result(arguments or {})
140
+ except Exception:
141
+ # Telemetry is fail-open; a read adapter failure must not
142
+ # terminate the HTTP process or touch the collector.
143
+ tool_value = {
144
+ "content": [
145
+ {
146
+ "type": "text",
147
+ "text": "FlowPilot telemetry is temporarily unavailable.",
148
+ }
149
+ ],
150
+ "structuredContent": adapter.unavailable_telemetry(),
151
+ "isError": True,
152
+ }
153
+ result = _response(request_id if has_id else None, result=tool_value)
154
+ else:
155
+ result = _response(
156
+ request_id if has_id else None,
157
+ error=_error(-32601, "Method not found"),
158
+ )
159
+
160
+ return None if not has_id else result
161
+
162
+
163
+ def dispatch_json(value: Any) -> Optional[Any]:
164
+ """Dispatch a single request or a JSON-RPC batch."""
165
+
166
+ if isinstance(value, list):
167
+ if not value:
168
+ return _response(None, error=_error(-32600, "Invalid Request"))
169
+ responses = [_dispatch(item) for item in value]
170
+ responses = [item for item in responses if item is not None]
171
+ return responses or None
172
+ return _dispatch(value)
173
+
174
+
175
+ def _is_loopback_host(host: str) -> bool:
176
+ normalized = host.strip().lower()
177
+ if normalized == "localhost":
178
+ return True
179
+ try:
180
+ return ipaddress.ip_address(normalized).is_loopback
181
+ except ValueError:
182
+ return False
183
+
184
+
185
+ class LoopbackHTTPServer(ThreadingHTTPServer):
186
+ daemon_threads = True
187
+ allow_reuse_address = True
188
+
189
+
190
+ class LoopbackHTTPServerV6(LoopbackHTTPServer):
191
+ address_family = __import__("socket").AF_INET6
192
+
193
+
194
+ class MCPRequestHandler(BaseHTTPRequestHandler):
195
+ protocol_version = "HTTP/1.1"
196
+
197
+ def log_message(self, format_string: str, *args: Any) -> None:
198
+ # A long-running local adapter should not fill stderr with access logs.
199
+ return
200
+
201
+ def _headers(self) -> None:
202
+ self.send_header("Cache-Control", "no-store")
203
+ self.send_header("Access-Control-Allow-Origin", "*")
204
+ self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
205
+ self.send_header(
206
+ "Access-Control-Allow-Headers",
207
+ "Content-Type, Accept, Mcp-Session-Id, MCP-Protocol-Version",
208
+ )
209
+
210
+ def _send_json(self, status: int, value: Any) -> None:
211
+ data = json.dumps(value, ensure_ascii=False, separators=(",", ":"), allow_nan=False).encode("utf-8")
212
+ self.send_response(status)
213
+ self._headers()
214
+ self.send_header("Content-Type", "application/json")
215
+ self.send_header("Content-Length", str(len(data)))
216
+ self.end_headers()
217
+ self.wfile.write(data)
218
+
219
+ def _send_empty(self, status: int) -> None:
220
+ self.send_response(status)
221
+ self._headers()
222
+ self.send_header("Content-Length", "0")
223
+ self.end_headers()
224
+
225
+ def do_OPTIONS(self) -> None:
226
+ if self.path not in ("/mcp", "/healthz"):
227
+ self._send_empty(HTTPStatus.NOT_FOUND)
228
+ return
229
+ self._send_empty(HTTPStatus.NO_CONTENT)
230
+
231
+ def do_GET(self) -> None:
232
+ if self.path == "/healthz":
233
+ self._send_json(
234
+ HTTPStatus.OK,
235
+ {"ok": True, "service": SERVER_NAME, "version": SERVER_VERSION},
236
+ )
237
+ return
238
+ self._send_json(HTTPStatus.NOT_FOUND, {"error": "Not found"})
239
+
240
+ def _read_json(self) -> Tuple[Optional[Any], Optional[Tuple[int, str]]]:
241
+ raw_length = self.headers.get("Content-Length")
242
+ if raw_length is None:
243
+ return None, (HTTPStatus.LENGTH_REQUIRED, "Content-Length is required")
244
+ try:
245
+ length = int(raw_length)
246
+ except (TypeError, ValueError):
247
+ return None, (HTTPStatus.BAD_REQUEST, "Invalid Content-Length")
248
+ if length < 0 or length > MAX_REQUEST_BYTES:
249
+ return None, (HTTPStatus.REQUEST_ENTITY_TOO_LARGE, "Request body is too large")
250
+ try:
251
+ raw = self.rfile.read(length)
252
+ value = json.loads(raw.decode("utf-8"))
253
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError):
254
+ return None, (HTTPStatus.BAD_REQUEST, "Parse error")
255
+ return value, None
256
+
257
+ def do_POST(self) -> None:
258
+ if self.path != "/mcp":
259
+ self._send_json(HTTPStatus.NOT_FOUND, {"error": "Not found"})
260
+ return
261
+ value, read_error = self._read_json()
262
+ if read_error is not None:
263
+ status, message = read_error
264
+ self._send_json(
265
+ status,
266
+ _response(None, error=_error(-32700, message)),
267
+ )
268
+ return
269
+ try:
270
+ result = dispatch_json(value)
271
+ except Exception:
272
+ # Never propagate an adapter or dispatch failure into the server
273
+ # loop. The collector is a separate process and remains untouched.
274
+ result = _response(None, error=_error(-32603, "Internal error"))
275
+ if result is None:
276
+ self._send_empty(HTTPStatus.NO_CONTENT)
277
+ else:
278
+ self._send_json(HTTPStatus.OK, result)
279
+
280
+
281
+ def _make_server(host: str, port: int) -> LoopbackHTTPServer:
282
+ if not _is_loopback_host(host):
283
+ raise ValueError("--host must be a loopback address")
284
+ server_class = LoopbackHTTPServerV6 if ":" in host else LoopbackHTTPServer
285
+ return server_class((host, port), MCPRequestHandler)
286
+
287
+
288
+ def build_parser() -> argparse.ArgumentParser:
289
+ parser = argparse.ArgumentParser(description="FlowPilot telemetry MCP server")
290
+ parser.add_argument("--host", default="127.0.0.1", help="loopback bind address")
291
+ parser.add_argument("--port", default=8787, type=int, help="TCP port (default: 8787)")
292
+ parser.add_argument("--stdio", action="store_true", help="run MCP server over standard I/O (JSON-RPC lines)")
293
+ return parser
294
+
295
+
296
+ def run_stdio() -> int:
297
+ """Run standard JSON-RPC loop over stdin/stdout."""
298
+ for line in sys.stdin:
299
+ raw = line.strip()
300
+ if not raw:
301
+ continue
302
+ try:
303
+ value = json.loads(raw)
304
+ except (json.JSONDecodeError, UnicodeDecodeError):
305
+ resp = _response(None, error=_error(-32700, "Parse error"))
306
+ sys.stdout.write(json.dumps(resp) + "\n")
307
+ sys.stdout.flush()
308
+ continue
309
+ try:
310
+ result = dispatch_json(value)
311
+ except Exception:
312
+ result = _response(None, error=_error(-32603, "Internal error"))
313
+ if result is not None:
314
+ sys.stdout.write(json.dumps(result) + "\n")
315
+ sys.stdout.flush()
316
+ return 0
317
+
318
+
319
+ def main(argv: Optional[List[str]] = None) -> int:
320
+ args = build_parser().parse_args(argv)
321
+ if args.stdio:
322
+ return run_stdio()
323
+ if args.port < 0 or args.port > 65535:
324
+ print("--port must be between 0 and 65535", file=sys.stderr)
325
+ return 2
326
+ try:
327
+ server = _make_server(args.host, args.port)
328
+ except (OSError, ValueError) as exc:
329
+ print("Unable to bind FlowPilot MCP server: %s" % exc, file=sys.stderr)
330
+ return 1
331
+
332
+ def stop(signum: int, frame: Any) -> None:
333
+ # ``shutdown`` must run off the serve_forever thread to avoid waiting
334
+ # on itself when SIGINT/SIGTERM arrives in the main thread.
335
+ threading.Thread(target=server.shutdown, daemon=True).start()
336
+
337
+ previous: Dict[int, Any] = {}
338
+ for signal_number in (signal.SIGINT, signal.SIGTERM):
339
+ try:
340
+ previous[signal_number] = signal.signal(signal_number, stop)
341
+ except (ValueError, OSError):
342
+ pass
343
+ try:
344
+ server.serve_forever(poll_interval=0.2)
345
+ except KeyboardInterrupt:
346
+ pass
347
+ finally:
348
+ server.server_close()
349
+ for signal_number, handler in previous.items():
350
+ try:
351
+ signal.signal(signal_number, handler)
352
+ except (ValueError, OSError):
353
+ pass
354
+ return 0
355
+
356
+
357
+ if __name__ == "__main__":
358
+ raise SystemExit(main())