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,223 @@
1
+ """Deterministic historical telemetry backfill and repair engine."""
2
+ from __future__ import annotations
3
+
4
+ import copy
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from .app_server import (
9
+ enrich_run_metadata,
10
+ extract_transcript_insights,
11
+ find_session_transcript,
12
+ quota_delta,
13
+ transcript_turn_usage,
14
+ )
15
+ from .common import (
16
+ LAST_FILE,
17
+ atomic_json,
18
+ iter_run_files,
19
+ now_ms,
20
+ numeric_ms,
21
+ read_json_object,
22
+ )
23
+
24
+
25
+ def _has_summary(val: Any) -> bool:
26
+ if not isinstance(val, dict):
27
+ return False
28
+ return any(bool(v) for v in val.values())
29
+
30
+
31
+ def repair_run(
32
+ run: dict[str, Any],
33
+ *,
34
+ report: dict[str, Any] | None = None,
35
+ ) -> dict[str, Any]:
36
+ """Repair a single telemetry run in-place by backfilling recoverable fields.
37
+
38
+ Principles:
39
+ 1. Only fills missing fields, never overwrites existing valid data.
40
+ 2. Safe skips when transcript is missing or unreadable.
41
+ 3. Backfills quota delta only if both before and after snapshots exist.
42
+ """
43
+ if not isinstance(run, dict):
44
+ return run
45
+
46
+ before_summary = _has_summary(run.get("summary_info"))
47
+ before_skills = bool(run.get("skills_used"))
48
+ before_tools = bool(run.get("tools_used"))
49
+ before_trajectory = bool(run.get("trajectory"))
50
+ before_logs = bool(run.get("logs"))
51
+ before_quota = bool(run.get("quota_change_during_run"))
52
+
53
+ transcript_path = run.get("transcript_path")
54
+ turn_id = run.get("turn_id")
55
+ has_valid_transcript = (
56
+ isinstance(transcript_path, str)
57
+ and bool(transcript_path)
58
+ and Path(transcript_path).is_file()
59
+ )
60
+
61
+ if not has_valid_transcript:
62
+ thread_path = (
63
+ run.get("thread", {}).get("path")
64
+ if isinstance(run.get("thread"), dict)
65
+ else None
66
+ )
67
+ if thread_path and Path(thread_path).is_file():
68
+ transcript_path = thread_path
69
+ run["transcript_path"] = thread_path
70
+ has_valid_transcript = True
71
+ else:
72
+ resolved = find_session_transcript(run.get("session_id"))
73
+ if resolved and Path(resolved).is_file():
74
+ transcript_path = resolved
75
+ run["transcript_path"] = resolved
76
+ has_valid_transcript = True
77
+
78
+ if has_valid_transcript:
79
+ insights = extract_transcript_insights(transcript_path, turn_id)
80
+ if isinstance(insights, dict):
81
+ for key in ("skills_used", "tools_used", "trajectory", "logs"):
82
+ if not run.get(key) and insights.get(key):
83
+ run[key] = insights[key]
84
+ if not before_summary and _has_summary(insights.get("summary_info")):
85
+ run["summary_info"] = insights["summary_info"]
86
+
87
+ parent = run.get("parent")
88
+ if isinstance(parent, dict):
89
+ u = parent.get("usage_delta")
90
+ total = u.get("total_tokens") if isinstance(u, dict) else None
91
+ if total is None or total == 0:
92
+ recovered_usage = transcript_turn_usage(transcript_path, turn_id)
93
+ if recovered_usage and recovered_usage.get("total_tokens", 0) > 0:
94
+ parent["usage_delta"] = recovered_usage
95
+ if report is not None:
96
+ report["tokens_restored"] = True
97
+
98
+ parent_model = (run.get("parent") or {}).get("model") if isinstance(run.get("parent"), dict) else None
99
+ if (run.get("cwd") == "/" or (parent_model == "gpt-5.6-terra" and not has_valid_transcript)) and not run.get("is_system_task"):
100
+ run["is_system_task"] = True
101
+
102
+ if run.get("finished_at_ms") is None and run.get("prompt_seen") is True:
103
+ started = numeric_ms(run.get("started_at_ms"))
104
+ if started and (now_ms() - started) > 300_000:
105
+ if not run.get("status") or run.get("status") == "running":
106
+ run["status"] = "aborted"
107
+ if report is not None:
108
+ report["aborted_marked"] = True
109
+
110
+ enrich_run_metadata(run)
111
+
112
+ quota_change = run.get("quota_change_during_run")
113
+ if not quota_change:
114
+ before = run.get("quota_before")
115
+ after = run.get("quota_after")
116
+ if isinstance(before, list) and before and isinstance(after, list) and after:
117
+ run["quota_change_during_run"] = quota_delta(before, after)
118
+
119
+ if report is not None:
120
+ report["task_summaries_restored"] = not before_summary and _has_summary(run.get("summary_info"))
121
+ report["skills_tools_restored"] = (
122
+ (not before_skills and bool(run.get("skills_used")))
123
+ or (not before_tools and bool(run.get("tools_used")))
124
+ )
125
+ report["trajectories_restored"] = not before_trajectory and bool(run.get("trajectory"))
126
+ report["logs_restored"] = not before_logs and bool(run.get("logs"))
127
+ if not before_quota:
128
+ report["quota_deltas_restored"] = bool(run.get("quota_change_during_run"))
129
+ report["quota_deltas_impossible"] = not bool(run.get("quota_change_during_run"))
130
+ else:
131
+ report["quota_deltas_restored"] = False
132
+ report["quota_deltas_impossible"] = False
133
+ report["transcript_missing"] = not has_valid_transcript
134
+
135
+ return run
136
+
137
+
138
+ def format_repair_summary(stats: dict[str, int]) -> str:
139
+ val_width = max((len(str(v)) for v in stats.values()), default=1)
140
+ val_width = max(val_width, 2)
141
+ col_width = 25
142
+
143
+ lines = [
144
+ "Telemetry repair complete\n",
145
+ f"{'scanned:':<{col_width}}{stats.get('scanned', 0):>{val_width}}",
146
+ f"{'repaired:':<{col_width}}{stats.get('repaired', 0):>{val_width}}",
147
+ f"{'unchanged:':<{col_width}}{stats.get('unchanged', 0):>{val_width}}",
148
+ f"{'transcript missing:':<{col_width}}{stats.get('transcript_missing', 0):>{val_width}}",
149
+ "",
150
+ f"{'task summaries restored:':<{col_width}}{stats.get('task_summaries_restored', 0):>{val_width}}",
151
+ f"{'skills/tools restored:':<{col_width}}{stats.get('skills_tools_restored', 0):>{val_width}}",
152
+ f"{'trajectories restored:':<{col_width}}{stats.get('trajectories_restored', 0):>{val_width}}",
153
+ f"{'logs restored:':<{col_width}}{stats.get('logs_restored', 0):>{val_width}}",
154
+ f"{'quota deltas restored:':<{col_width}}{stats.get('quota_deltas_restored', 0):>{val_width}}",
155
+ f"{'quota deltas impossible:':<{col_width}}{stats.get('quota_deltas_impossible', 0):>{val_width}}",
156
+ ]
157
+ return "\n".join(lines)
158
+
159
+
160
+ def repair_history(dry_run: bool = False, verbose: bool = True) -> dict[str, int]:
161
+ """Scan and repair all historical run records deterministically."""
162
+ stats = {
163
+ "scanned": 0,
164
+ "repaired": 0,
165
+ "unchanged": 0,
166
+ "transcript_missing": 0,
167
+ "task_summaries_restored": 0,
168
+ "skills_tools_restored": 0,
169
+ "trajectories_restored": 0,
170
+ "logs_restored": 0,
171
+ "quota_deltas_restored": 0,
172
+ "quota_deltas_impossible": 0,
173
+ }
174
+
175
+ last = read_json_object(LAST_FILE)
176
+
177
+ for path in iter_run_files():
178
+ run = read_json_object(path)
179
+ if not isinstance(run, dict):
180
+ continue
181
+
182
+ stats["scanned"] += 1
183
+
184
+ run_report: dict[str, Any] = {}
185
+ original = copy.deepcopy(run)
186
+
187
+ repair_run(run, report=run_report)
188
+
189
+ changed = (run != original)
190
+
191
+ if run_report.get("task_summaries_restored"):
192
+ stats["task_summaries_restored"] += 1
193
+ if run_report.get("skills_tools_restored"):
194
+ stats["skills_tools_restored"] += 1
195
+ if run_report.get("trajectories_restored"):
196
+ stats["trajectories_restored"] += 1
197
+ if run_report.get("logs_restored"):
198
+ stats["logs_restored"] += 1
199
+ if run_report.get("quota_deltas_restored"):
200
+ stats["quota_deltas_restored"] += 1
201
+ if run_report.get("quota_deltas_impossible"):
202
+ stats["quota_deltas_impossible"] += 1
203
+
204
+ if changed:
205
+ stats["repaired"] += 1
206
+ if not dry_run:
207
+ atomic_json(path, run)
208
+ if (
209
+ isinstance(last, dict)
210
+ and last.get("session_id") == run.get("session_id")
211
+ and last.get("turn_id") == run.get("turn_id")
212
+ ):
213
+ atomic_json(LAST_FILE, run)
214
+ else:
215
+ if run_report.get("transcript_missing"):
216
+ stats["transcript_missing"] += 1
217
+ else:
218
+ stats["unchanged"] += 1
219
+
220
+ if verbose:
221
+ print(format_repair_summary(stats))
222
+
223
+ return stats
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env python3
2
+ """Localized user-facing CLI surfaces for codex-flow."""
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ import re
8
+ from pathlib import Path
9
+
10
+ from localization import (
11
+ configured_language,
12
+ detect_system_language,
13
+ resolve_language,
14
+ set_configured_language,
15
+ tr,
16
+ )
17
+
18
+ CODEX_HOME = Path(os.environ.get("CODEX_HOME", Path.home() / ".codex"))
19
+ STATE_DIR = CODEX_HOME / "codex-flow"
20
+ POLICY = CODEX_HOME / "codex-flow.toml"
21
+
22
+
23
+ def T(en: str, zh: str, lang: str) -> str:
24
+ return tr(en, zh, lang=lang)
25
+
26
+
27
+ def display_width(text: str) -> int:
28
+ import unicodedata
29
+ return sum(2 if unicodedata.east_asian_width(ch) in ("W", "F") else 1 for ch in text)
30
+
31
+
32
+ def policy_value(section: str, key: str, default: str = "") -> str:
33
+ try:
34
+ text = POLICY.read_text(encoding="utf-8")
35
+ except OSError:
36
+ return default
37
+ m = re.search(rf"(?ms)^\[{re.escape(section)}\]\s*\n(.*?)(?=^\[[^\n]+\]\s*$|\Z)", text)
38
+ if not m:
39
+ return default
40
+ k = re.search(rf"(?m)^\s*{re.escape(key)}\s*=\s*(.*?)\s*$", m.group(1))
41
+ if not k:
42
+ return default
43
+ value = re.sub(r"\s+#.*$", "", k.group(1)).strip()
44
+ if len(value) >= 2 and value[0] == value[-1] == '"':
45
+ value = value[1:-1]
46
+ return value or default
47
+
48
+
49
+ def source_dir() -> Path | None:
50
+ try:
51
+ path = Path((STATE_DIR / "source").read_text(encoding="utf-8").strip())
52
+ return path if path.exists() else None
53
+ except OSError:
54
+ return None
55
+
56
+
57
+ def compact_path(path: Path) -> str:
58
+ try:
59
+ home = str(Path.home())
60
+ text = str(path)
61
+ return "~" + text[len(home):] if text.startswith(home) else text
62
+ except Exception:
63
+ return str(path)
64
+
65
+
66
+ def box_line(content: str, width: int = 68) -> str:
67
+ return f" │ {content}{' ' * max(0, width - display_width(content))} │"
68
+
69
+
70
+ def status(lang: str) -> int:
71
+ src = source_dir()
72
+ installed = "unknown"
73
+ available = "unknown"
74
+ try:
75
+ installed = (STATE_DIR / "version").read_text(encoding="utf-8").strip() or installed
76
+ except OSError:
77
+ pass
78
+ if src:
79
+ try:
80
+ available = (src / "VERSION").read_text(encoding="utf-8").strip() or available
81
+ except OSError:
82
+ pass
83
+
84
+ print(f"\n📦 {T('codex-flow status', 'codex-flow 状态', lang)}\n")
85
+ print(" ╭─ " + T("Version & Paths", "版本与路径", lang) + " " + "─" * (51 if lang == "en" else 55) + "╮")
86
+ print(box_line(f"• {T('Installed', '已安装', lang)}: v{installed}"))
87
+ if src:
88
+ print(box_line(f"• {T('Checkout', '源码目录', lang)}: {compact_path(src)} (v{available})"))
89
+ else:
90
+ print(box_line(f"• {T('Checkout', '源码目录', lang)}: {T('unavailable', '不可用', lang)}"))
91
+ print(box_line(f"• {T('Runtime', '运行时', lang)}: FlowPilot (flow-pilot)"))
92
+ if POLICY.exists():
93
+ print(" ├─ " + T("Strategy & Model Routing", "策略与模型路由", lang) + " " + "─" * (40 if lang == "en" else 46) + "┤")
94
+ strategy_enabled = policy_value("strategy", "enabled", "true")
95
+ strategy = policy_value("strategy", "profile", "efficient")
96
+ routing = policy_value("routing", "mode", "adaptive")
97
+ p_policy = policy_value("parent", "model_policy", "unknown")
98
+ p_effort = policy_value("parent", "min_reasoning_effort", "unknown")
99
+ w_model = policy_value("worker", "resolved_model", "unknown")
100
+ w_effort = policy_value("worker", "min_reasoning_effort", "unknown")
101
+ rollout_mode = policy_value("reasoning.rollout", "mode", "shadow")
102
+ rollout_map = "/".join(
103
+ policy_value("reasoning.rollout", key, fallback)
104
+ for key, fallback in (
105
+ ("minimum", "high"),
106
+ ("routine", "high"),
107
+ ("complex", "xhigh"),
108
+ ("critical", "max"),
109
+ )
110
+ )
111
+ t_enabled = policy_value("telemetry", "enabled", "true")
112
+ t_notify = policy_value("telemetry", "notifications", "false")
113
+ retention = policy_value("telemetry", "retention_days", "30")
114
+ configured = configured_language(POLICY)
115
+ effective = resolve_language(POLICY)
116
+ state = "● " + T("enabled", "已启用", lang) if strategy_enabled == "true" else "○ " + T("disabled", "已禁用", lang)
117
+ print(box_line(f"• {T('Strategy', '执行策略', lang)}: {strategy} ({state})"))
118
+ print(box_line(f"• {T('Routing', '路由模式', lang)}: {routing}"))
119
+ print(box_line(f"• {T('Reasoning rollout', '推理 rollout', lang)}: {rollout_mode} ({rollout_map})"))
120
+ print(box_line(f"• {T('Parent', '父 Agent', lang)}: {p_policy} ({T('min effort', '最低推理', lang)}: {p_effort})"))
121
+ print(box_line(f"• {T('Worker', '子 Agent', lang)}: {w_model} ({T('min effort', '最低推理', lang)}: {w_effort})"))
122
+ print(box_line(f"• {T('Telemetry', '遥测', lang)}: {'● ' + T('enabled','已启用',lang) if t_enabled == 'true' else '○ ' + T('disabled','已禁用',lang)}"))
123
+ print(box_line(f"• {T('Notify', '通知', lang)}: {'● ' + T('system notification','系统通知',lang) if t_notify == 'true' else '○ ' + T('disabled','已禁用',lang)}"))
124
+ print(box_line(f"• {T('Retention', '保留周期', lang)}: {retention} {T('days per-run data', '天(单次任务数据)', lang)}"))
125
+ print(box_line(f"• {T('Language', '语言', lang)}: {configured} ({T('effective', '当前生效', lang)}: {effective})"))
126
+ print(" ╰" + "─" * 69 + "╯\n")
127
+ return 0
128
+
129
+
130
+ def language(args: list[str], lang: str) -> int:
131
+ if not args:
132
+ configured = configured_language(POLICY)
133
+ system = detect_system_language()
134
+ effective = resolve_language(POLICY)
135
+ print(T(
136
+ f"Language: configured={configured}, system={system}, effective={effective}",
137
+ f"语言:配置={configured},系统={system},当前生效={effective}",
138
+ lang,
139
+ ))
140
+ print(T(
141
+ "Set with: codex-flow language auto|zh|en",
142
+ "设置方式:codex-flow language auto|zh|en",
143
+ lang,
144
+ ))
145
+ return 0
146
+ if len(args) != 1:
147
+ print(T("Usage: codex-flow language auto|zh|en", "用法:codex-flow language auto|zh|en", lang))
148
+ return 2
149
+ try:
150
+ configured = set_configured_language(args[0], POLICY)
151
+ except ValueError:
152
+ print(T("Language must be auto, zh, or en.", "语言必须是 auto、zh 或 en。", lang))
153
+ return 2
154
+ effective = resolve_language(POLICY)
155
+ out_lang = effective
156
+ print(T(
157
+ f"Language set to {configured} (effective: {effective}).",
158
+ f"语言已设置为 {configured}(当前生效:{effective})。",
159
+ out_lang,
160
+ ))
161
+ return 0
162
+
163
+
164
+ def help_text(lang: str) -> int:
165
+ if lang == "zh":
166
+ print("""
167
+ 用法: codex-flow <命令> [选项]
168
+
169
+ 交互控制台
170
+ codex-flow 启动交互式管理控制台(TTY 下无参数)
171
+
172
+ 核心命令
173
+ status 查看版本与当前生效的 FlowPilot 策略
174
+ strategy show 查看持久化 strategy / routing
175
+ strategy profiles 查看 efficient/balanced/quality/speed
176
+ strategy set <profile> 设置默认执行策略
177
+ strategy enabled 查看策略分发总开关
178
+ strategy enable 开启 FlowPilot 自动策略分发
179
+ strategy disable 关闭 FlowPilot 自动策略分发
180
+ strategy routing <mode> 设置 adaptive/direct/delegate 路由约束
181
+ strategy plan [选项] 从结构化 TaskProfile 生成 ExecutionPlan(JSON)
182
+ language [auto|zh|en] 查看或设置界面语言(默认 auto 跟随系统)
183
+ update 拉取源码、保留策略并刷新安装
184
+ doctor 检查安装、策略运行时和遥测链路
185
+ overlay [start|stop|toggle] macOS 原生悬浮窗
186
+ usage last [--json] 查看最近一次任务遥测摘要
187
+ usage list [选项] 浏览任务历史(-n N、--project P、--today、--json)
188
+ usage show <#|id> [选项] 查看指定任务遥测摘要
189
+ usage stats [选项] 查看聚合遥测统计(--project P、--days N)
190
+ telemetry repair [--dry-run] 扫描并回填历史遥测可恢复字段(支持 --json)
191
+ telemetry latency report [--json] 查看脱敏 Worker 延迟 p50/p95
192
+
193
+ Benchmark 命令
194
+ benchmark-local 使用本地 Codex 会话运行内置 Benchmark
195
+ benchmark-corpus 仅物化测试语料,不调用模型
196
+ benchmark 运行可复现的 Codex Benchmark manifest
197
+ benchmark-analyze 分析 Benchmark JSONL
198
+
199
+ 维护
200
+ uninstall 移除 codex-flow 管理的文件与 hooks
201
+
202
+ 提示:
203
+ 默认策略为 efficient,路由为 adaptive;这与旧版 FlowPilot 行为兼容。
204
+ strategy disable 只关闭 codex-flow 自动分发,不关闭 Codex 原生 Agent 能力。
205
+ 对话中的明确策略/路由指令只覆盖当前任务,不修改持久化配置。
206
+ """)
207
+ else:
208
+ print("""
209
+ Usage: codex-flow <command> [options]
210
+
211
+ Interactive Console
212
+ codex-flow Launch the interactive management console (no args in TTY)
213
+
214
+ Core Commands
215
+ status Show installed version and effective FlowPilot strategy
216
+ strategy show Show persistent strategy / routing
217
+ strategy profiles List efficient/balanced/quality/speed profiles
218
+ strategy set <profile> Set the default execution strategy
219
+ strategy enabled Show the strategy dispatch master switch
220
+ strategy enable Enable FlowPilot automatic strategy dispatch
221
+ strategy disable Disable FlowPilot automatic strategy dispatch
222
+ strategy routing <mode> Set adaptive/direct/delegate routing constraint
223
+ strategy plan [options] Compile a structured TaskProfile to ExecutionPlan JSON
224
+ language [auto|zh|en] Show or set UI language (auto follows the system)
225
+ update Pull checkout, preserve policy, refresh installation
226
+ doctor Verify installation, strategy runtime, and telemetry wiring
227
+ overlay [start|stop|toggle] Native macOS floating widget
228
+ usage last [--json] Show the last task telemetry summary
229
+ usage list [options] List task history (-n N, --project P, --today, --json)
230
+ usage show <#|id> [opt] Show specific task telemetry summary
231
+ usage stats [options] Show aggregated telemetry stats (--project P, --days N)
232
+ telemetry repair [--dry-run] Backfill recoverable fields in history (supports --json)
233
+ telemetry latency report [--json] Show redacted Worker latency p50/p95
234
+
235
+ Benchmark Commands
236
+ benchmark-local Run built-in benchmark via local Codex session
237
+ benchmark-corpus Materialize corpus without calling any model
238
+ benchmark Run a reproducible Codex benchmark manifest
239
+ benchmark-analyze Analyze benchmark JSONL
240
+
241
+ Maintenance
242
+ uninstall Remove codex-flow-managed files and hooks
243
+
244
+ Tip:
245
+ The default is efficient + adaptive, preserving previous FlowPilot behavior.
246
+ strategy disable turns off codex-flow auto-dispatch, not Codex native Agent capability.
247
+ Explicit strategy/routing instructions in a prompt override only the current task.
248
+ """)
249
+ return 0
250
+
251
+
252
+ def main() -> int:
253
+ parser = argparse.ArgumentParser(add_help=False)
254
+ parser.add_argument("command", nargs="?", default="help")
255
+ parser.add_argument("rest", nargs="*")
256
+ ns = parser.parse_args()
257
+ lang = resolve_language(POLICY)
258
+ if ns.command == "status":
259
+ return status(lang)
260
+ if ns.command == "language":
261
+ return language(ns.rest, lang)
262
+ return help_text(lang)
263
+
264
+
265
+ if __name__ == "__main__":
266
+ raise SystemExit(main())
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env python3
2
+ """Update Homebrew Formula for codex-flow using release manifests."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import subprocess
10
+ import tempfile
11
+ from pathlib import Path
12
+
13
+ ROOT = Path(__file__).resolve().parents[1]
14
+ FORMULA_PATH = ROOT / "packaging" / "homebrew" / "codex-flow.rb"
15
+
16
+ TEMPLATE = """class CodexFlow < Formula
17
+ desc "Intelligent, Efficient, Adaptive Multi-Agent Strategy Orchestration for Codex"
18
+ homepage "https://github.com/ParsifalC/codex-flow"
19
+ version "{version}"
20
+
21
+ on_macos do
22
+ if Hardware::CPU.arm?
23
+ url "{darwin_arm64_url}"
24
+ sha256 "{darwin_arm64_sha}"
25
+ else
26
+ url "{darwin_x86_64_url}"
27
+ sha256 "{darwin_x86_64_sha}"
28
+ end
29
+ end
30
+
31
+ on_linux do
32
+ if Hardware::CPU.arm?
33
+ url "{linux_arm64_url}"
34
+ sha256 "{linux_arm64_sha}"
35
+ else
36
+ url "{linux_x86_64_url}"
37
+ sha256 "{linux_x86_64_sha}"
38
+ end
39
+ end
40
+
41
+ def install
42
+ libexec.install Dir["*"]
43
+ bin.write_exec_script libexec/"bin/codex-flow"
44
+ bin.write_exec_script libexec/"bin/codex-flow-mcp"
45
+ bash_completion.install libexec/"completions/codex-flow.bash" => "codex-flow" if (libexec/"completions/codex-flow.bash").exist?
46
+ end
47
+
48
+ def caveats
49
+ <<~EOS
50
+ To initialize FlowPilot into your Codex environment, run:
51
+ codex-flow install
52
+
53
+ Or launch the interactive terminal console:
54
+ codex-flow
55
+ EOS
56
+ end
57
+
58
+ test do
59
+ assert_match "codex-flow", shell_output("#{{bin}}/codex-flow --help", 0)
60
+ end
61
+ end
62
+ """
63
+
64
+
65
+ def sync_to_tap(repo: str, token: str, branch: str, formula_content: str, version: str) -> None:
66
+ with tempfile.TemporaryDirectory() as tmp_dir:
67
+ clone_url = f"https://x-access-token:{token}@github.com/{repo}.git"
68
+ print(f"Cloning {repo} ({branch})...")
69
+ subprocess.run(
70
+ ["git", "clone", "--depth", "1", "--branch", branch, clone_url, tmp_dir],
71
+ check=True,
72
+ stdout=subprocess.PIPE,
73
+ stderr=subprocess.PIPE,
74
+ )
75
+ target = Path(tmp_dir) / "Formula" / "codex-flow.rb"
76
+ target.parent.mkdir(parents=True, exist_ok=True)
77
+ target.write_text(formula_content, encoding="utf-8")
78
+
79
+ status = subprocess.run(
80
+ ["git", "-C", tmp_dir, "status", "--porcelain"],
81
+ capture_output=True,
82
+ text=True,
83
+ check=True,
84
+ )
85
+ if not status.stdout.strip():
86
+ print(f"Formula in {repo} is already up to date for version {version}")
87
+ return
88
+
89
+ subprocess.run(["git", "-C", tmp_dir, "config", "user.name", "Parsifal"], check=True)
90
+ subprocess.run(["git", "-C", tmp_dir, "config", "user.email", "parsifal@users.noreply.github.com"], check=True)
91
+ subprocess.run(["git", "-C", tmp_dir, "add", "Formula/codex-flow.rb"], check=True)
92
+ subprocess.run(["git", "-C", tmp_dir, "commit", "-m", f"chore(formula): update codex-flow to v{version}"], check=True)
93
+ subprocess.run(["git", "-C", tmp_dir, "push", "origin", branch], check=True)
94
+ print(f"Successfully pushed updated formula to {repo} ({branch}) for version {version}")
95
+
96
+
97
+ def main() -> int:
98
+ parser = argparse.ArgumentParser(description="Update Homebrew formula for codex-flow")
99
+ parser.add_argument("--manifest", type=Path, default=ROOT / "dist" / "codex-flow-update.json")
100
+ parser.add_argument("--output", type=Path, default=FORMULA_PATH)
101
+ parser.add_argument("--tap-repo", type=str, default="", help="GitHub tap repo (e.g. ParsifalC/homebrew-tap)")
102
+ parser.add_argument("--tap-token", type=str, default=os.getenv("HOMEBREW_TAP_TOKEN", ""), help="GitHub token for tap repo")
103
+ parser.add_argument("--tap-branch", type=str, default="main", help="Tap branch to push to")
104
+ args = parser.parse_args()
105
+
106
+ if not args.manifest.exists():
107
+ raise SystemExit(f"Manifest not found: {args.manifest}")
108
+
109
+ data = json.loads(args.manifest.read_text(encoding="utf-8"))
110
+ version = data["version"]
111
+ artifacts = data["artifacts"]
112
+
113
+ content = TEMPLATE.format(
114
+ version=version,
115
+ darwin_arm64_url=artifacts["darwin-arm64"]["url"],
116
+ darwin_arm64_sha=artifacts["darwin-arm64"]["sha256"],
117
+ darwin_x86_64_url=artifacts["darwin-x86_64"]["url"],
118
+ darwin_x86_64_sha=artifacts["darwin-x86_64"]["sha256"],
119
+ linux_arm64_url=artifacts["linux-arm64"]["url"],
120
+ linux_arm64_sha=artifacts["linux-arm64"]["sha256"],
121
+ linux_x86_64_url=artifacts["linux-x86_64"]["url"],
122
+ linux_x86_64_sha=artifacts["linux-x86_64"]["sha256"],
123
+ )
124
+
125
+ args.output.parent.mkdir(parents=True, exist_ok=True)
126
+ args.output.write_text(content, encoding="utf-8")
127
+ print(f"Updated {args.output} for version {version}")
128
+
129
+ if args.tap_repo:
130
+ token = args.tap_token.strip()
131
+ if not token:
132
+ print(f"::warning::HOMEBREW_TAP_TOKEN is not provided. Skipping push to {args.tap_repo}.")
133
+ else:
134
+ sync_to_tap(
135
+ repo=args.tap_repo,
136
+ token=token,
137
+ branch=args.tap_branch,
138
+ formula_content=content,
139
+ version=version,
140
+ )
141
+
142
+ return 0
143
+
144
+
145
+ if __name__ == "__main__":
146
+ raise SystemExit(main())