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.
- codex_flow/__init__.py +28 -0
- codex_flow/__main__.py +9 -0
- codex_flow/cli.py +242 -0
- codex_flow/data/LICENSE +21 -0
- codex_flow/data/README.en.md +303 -0
- codex_flow/data/README.md +305 -0
- codex_flow/data/VERSION +1 -0
- codex_flow/data/apps/chatgpt-mcp/README.md +86 -0
- codex_flow/data/apps/chatgpt-mcp/__init__.py +1 -0
- codex_flow/data/apps/chatgpt-mcp/adapter.py +458 -0
- codex_flow/data/apps/chatgpt-mcp/server.py +358 -0
- codex_flow/data/apps/chatgpt-mcp/widget.html +927 -0
- codex_flow/data/apps/macos-overlay/README.en.md +121 -0
- codex_flow/data/apps/macos-overlay/README.md +123 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayRuntimeState.swift +126 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayScreenGeometry.swift +82 -0
- codex_flow/data/apps/macos-overlay/Sources/Controllers/OverlayWindowController.swift +1052 -0
- codex_flow/data/apps/macos-overlay/Sources/Localization.swift +197 -0
- codex_flow/data/apps/macos-overlay/Sources/Models/TelemetryData.swift +1557 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/AccountSnapshotService.swift +1101 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/FlowPilotInstanceLock.swift +153 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/IPCServer.swift +298 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/TelemetryQueryEngine.swift +800 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/TelemetryWatcher.swift +135 -0
- codex_flow/data/apps/macos-overlay/Sources/Services/UpdateService.swift +610 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AccountView.swift +610 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AnalyticsView.swift +566 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/AutostartView.swift +293 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/BubbleView.swift +317 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/HistoryView.swift +1124 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/HoverRevealText.swift +165 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/InspectorSkillsToolsView.swift +121 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/LogoView.swift +182 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/SleekSwitch.swift +117 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/StrategyModeView.swift +561 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/SummaryView.swift +1273 -0
- codex_flow/data/apps/macos-overlay/Sources/Views/UpdateView.swift +352 -0
- codex_flow/data/apps/macos-overlay/Sources/main.swift +340 -0
- codex_flow/data/apps/macos-overlay/Tests/OverlayScreenGeometryTests.swift +163 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryPhase1ContractTests.swift +357 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryQueryEngineConcurrencyTests.swift +221 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryQuotaSelectionTests.swift +158 -0
- codex_flow/data/apps/macos-overlay/Tests/TelemetryWorkerTokenTests.swift +122 -0
- codex_flow/data/apps/macos-overlay/build.sh +75 -0
- codex_flow/data/benchmark/corpus.json +103 -0
- codex_flow/data/benchmark/manifest.example.json +41 -0
- codex_flow/data/benchmark/manifest.schema.json +137 -0
- codex_flow/data/benchmark/prices/gpt-5.6-2026-08-30.json +5 -0
- codex_flow/data/benchmark/profiles.json +90 -0
- codex_flow/data/benchmark/schema.json +77 -0
- codex_flow/data/benchmark/tasks.json +50 -0
- codex_flow/data/completions/codex-flow.bash +34 -0
- codex_flow/data/completions/codex-flow.zsh +52 -0
- codex_flow/data/glama.json +6 -0
- codex_flow/data/install-release.ps1 +126 -0
- codex_flow/data/install-release.sh +155 -0
- codex_flow/data/install.ps1 +349 -0
- codex_flow/data/install.sh +362 -0
- codex_flow/data/policy/benchmark.toml +49 -0
- codex_flow/data/policy/defaults.toml +70 -0
- codex_flow/data/scripts/analyze-benchmark.py +510 -0
- codex_flow/data/scripts/benchmark-local.py +171 -0
- codex_flow/data/scripts/check-recommendation.py +277 -0
- codex_flow/data/scripts/doctor.py +449 -0
- codex_flow/data/scripts/generate-release-manifest.py +74 -0
- codex_flow/data/scripts/localization.py +192 -0
- codex_flow/data/scripts/manage-hooks.py +448 -0
- codex_flow/data/scripts/manage-instructions.py +389 -0
- codex_flow/data/scripts/manage-shell.py +151 -0
- codex_flow/data/scripts/materialize-corpus.py +193 -0
- codex_flow/data/scripts/menu.py +646 -0
- codex_flow/data/scripts/migrations/0001_update_settings.py +80 -0
- codex_flow/data/scripts/package-release.py +132 -0
- codex_flow/data/scripts/render-benchmark-report.py +292 -0
- codex_flow/data/scripts/run-benchmark.py +829 -0
- codex_flow/data/scripts/strategies/__init__.py +28 -0
- codex_flow/data/scripts/strategies/balanced.py +115 -0
- codex_flow/data/scripts/strategies/base.py +363 -0
- codex_flow/data/scripts/strategies/efficient.py +158 -0
- codex_flow/data/scripts/strategies/lifecycle_runtime.py +590 -0
- codex_flow/data/scripts/strategies/quality.py +209 -0
- codex_flow/data/scripts/strategies/speed.py +108 -0
- codex_flow/data/scripts/strategies/task_budget_runtime.py +644 -0
- codex_flow/data/scripts/strategies/task_phase_runtime.py +341 -0
- codex_flow/data/scripts/strategies/work_unit_runtime.py +421 -0
- codex_flow/data/scripts/strategy_runtime.py +1091 -0
- codex_flow/data/scripts/telemetry.py +400 -0
- codex_flow/data/scripts/telemetry_core/__init__.py +192 -0
- codex_flow/data/scripts/telemetry_core/app_server.py +1192 -0
- codex_flow/data/scripts/telemetry_core/collector.py +1247 -0
- codex_flow/data/scripts/telemetry_core/common.py +421 -0
- codex_flow/data/scripts/telemetry_core/latency.py +593 -0
- codex_flow/data/scripts/telemetry_core/query.py +427 -0
- codex_flow/data/scripts/telemetry_core/quota_ledger.py +598 -0
- codex_flow/data/scripts/telemetry_core/render.py +460 -0
- codex_flow/data/scripts/telemetry_core/repair.py +223 -0
- codex_flow/data/scripts/ui.py +266 -0
- codex_flow/data/scripts/update-homebrew-formula.py +146 -0
- codex_flow/data/scripts/update_runtime_config.py +134 -0
- codex_flow/data/scripts/updater.py +1718 -0
- codex_flow/data/smithery.yaml +18 -0
- codex_flow/data/templates/agents/worker-explorer.toml +24 -0
- codex_flow/data/templates/agents/worker-implementer.toml +49 -0
- codex_flow/data/templates/agents/worker-reviewer.toml +25 -0
- codex_flow/data/templates/flow-pilot-instructions.md +35 -0
- codex_flow/data/templates/skills/flow-pilot/SKILL.md +577 -0
- codex_flow/mcp.py +35 -0
- codex_flow-2.1.13.dist-info/METADATA +342 -0
- codex_flow-2.1.13.dist-info/RECORD +113 -0
- codex_flow-2.1.13.dist-info/WHEEL +5 -0
- codex_flow-2.1.13.dist-info/entry_points.txt +3 -0
- codex_flow-2.1.13.dist-info/licenses/LICENSE +21 -0
- 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())
|