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,341 @@
1
+ #!/usr/bin/env python3
2
+ """Phase-aware admission for the canonical ExecutionPlan task budget.
3
+
4
+ The compiler emits one self-consistent task budget. Its soft deadline is the
5
+ end of general writable work. If independent review is planned, its hard
6
+ deadline already includes the review-stage hard window plus an explicit Parent
7
+ finalization reserve. This helper validates that contract and performs
8
+ phase-aware reservations; it never derives a second budget.
9
+
10
+ Required completion has two admission windows:
11
+ - review may start from the general-work cutoff until ``review_deadline``;
12
+ - Parent finalization retains the remaining tail until the absolute hard stop.
13
+
14
+ A reviewer retry therefore cannot consume the Parent finalization reserve.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import hashlib
20
+ import json
21
+ import math
22
+ import sys
23
+ from dataclasses import asdict
24
+ from typing import Any
25
+
26
+ try:
27
+ from .base import TaskBudgetPolicy
28
+ from .task_budget_runtime import (
29
+ GENERAL_RESERVATION_KINDS,
30
+ LedgerError,
31
+ init_ledger,
32
+ ledger_status,
33
+ reserve,
34
+ )
35
+ except ImportError:
36
+ from base import TaskBudgetPolicy
37
+ from task_budget_runtime import (
38
+ GENERAL_RESERVATION_KINDS,
39
+ LedgerError,
40
+ init_ledger,
41
+ ledger_status,
42
+ reserve,
43
+ )
44
+
45
+ PHASES = ("exploration", "implementation", "required_completion")
46
+ RESERVATION_PHASE = {
47
+ "work_unit": "implementation",
48
+ "implementation_attempt": "implementation",
49
+ "replan": "implementation",
50
+ "replacement": "implementation",
51
+ "review_attempt": "required_completion",
52
+ }
53
+
54
+
55
+ def _strict_number(value: Any, label: str) -> float:
56
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
57
+ raise LedgerError(f"{label} must be a finite number")
58
+ number = float(value)
59
+ if not math.isfinite(number) or number < 0:
60
+ raise LedgerError(f"{label} must be a finite non-negative number")
61
+ return number
62
+
63
+
64
+ def _cli_now(value: Any) -> float:
65
+ if isinstance(value, bool):
66
+ raise LedgerError("now must be finite seconds")
67
+ try:
68
+ number = float(value)
69
+ except (TypeError, ValueError):
70
+ raise LedgerError("now must be finite seconds") from None
71
+ if not math.isfinite(number) or number < 0:
72
+ raise LedgerError("now must be finite non-negative seconds")
73
+ return number
74
+
75
+
76
+ def _strict_int(value: Any, label: str) -> int:
77
+ if type(value) is not int or value < 0:
78
+ raise LedgerError(f"{label} must be a non-negative integer")
79
+ return value
80
+
81
+
82
+ def _task_policy(plan: dict[str, Any]) -> TaskBudgetPolicy:
83
+ try:
84
+ return TaskBudgetPolicy.from_dict(plan["task_budget"])
85
+ except (TypeError, ValueError) as exc:
86
+ raise LedgerError(str(exc)) from None
87
+
88
+
89
+ def _policy_fingerprint(policy: TaskBudgetPolicy) -> str:
90
+ encoded = json.dumps(asdict(policy), ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
91
+ return hashlib.sha256(encoded).hexdigest()
92
+
93
+
94
+ def _completion_reserve(plan: dict[str, Any], policy: TaskBudgetPolicy) -> tuple[int, int, int]:
95
+ reviewer_workers = _strict_int(plan.get("reviewer_workers"), "reviewer_workers")
96
+ review_stage = plan.get("review_stage")
97
+ if reviewer_workers == 0:
98
+ if review_stage is not None:
99
+ raise LedgerError("review_stage must be null when reviewer_workers is zero")
100
+ if policy.max_review_attempts != 0:
101
+ raise LedgerError("max_review_attempts must be zero when reviewer_workers is zero")
102
+ return 0, 0, 0
103
+ if type(review_stage) is not dict:
104
+ raise LedgerError("review_stage is required when reviewer_workers is positive")
105
+ if policy.max_review_attempts < reviewer_workers:
106
+ raise LedgerError("review topology exceeds task budget max_review_attempts")
107
+ review_hard = _strict_int(review_stage.get("hard_timeout_seconds"), "review_stage.hard_timeout_seconds")
108
+ if review_hard < 1:
109
+ raise LedgerError("review_stage.hard_timeout_seconds must be positive")
110
+ parent_finalization = policy.parent_finalization_seconds
111
+ return review_hard, parent_finalization, review_hard + parent_finalization
112
+
113
+
114
+ def _plan(value: Any) -> dict[str, Any]:
115
+ if type(value) is not dict:
116
+ raise LedgerError("ExecutionPlan must be an object")
117
+ if value.get("schema_version") != 11:
118
+ raise LedgerError("task phase runtime requires ExecutionPlan schema v11")
119
+ if type(value.get("task_budget")) is not dict:
120
+ raise LedgerError("ExecutionPlan task_budget must be an object")
121
+ policy = _task_policy(value)
122
+ implementation_workers = _strict_int(value.get("implementation_workers"), "implementation_workers")
123
+ implementation_stage = value.get("implementation_stage")
124
+ if implementation_workers > 0:
125
+ if type(implementation_stage) is not dict:
126
+ raise LedgerError("implementation_stage is required when implementation_workers is positive")
127
+ maximum_work_units = implementation_stage.get("maximum_work_units")
128
+ if type(maximum_work_units) is not int or maximum_work_units < 1:
129
+ raise LedgerError("implementation_stage.maximum_work_units must be a positive integer")
130
+ if maximum_work_units != policy.max_work_units:
131
+ raise LedgerError("task budget max_work_units must match implementation_stage maximum_work_units")
132
+ if implementation_workers > policy.max_work_units:
133
+ raise LedgerError("implementation topology exceeds task budget max_work_units")
134
+ if implementation_workers > policy.max_implementation_attempts:
135
+ raise LedgerError("implementation topology exceeds task budget max_implementation_attempts")
136
+ implementation_soft = implementation_stage.get("soft_timeout_seconds")
137
+ if type(implementation_soft) is not int or implementation_soft < 1:
138
+ raise LedgerError("implementation_stage.soft_timeout_seconds must be a positive integer")
139
+ if policy.soft_timeout_seconds < implementation_soft:
140
+ raise LedgerError("task soft timeout cannot precede implementation soft checkpoint budget")
141
+ elif implementation_stage is not None:
142
+ raise LedgerError("implementation_stage must be null when implementation_workers is zero")
143
+
144
+ _review_hard, _parent_finalization, reserve_seconds = _completion_reserve(value, policy)
145
+ if reserve_seconds > 0 and policy.hard_timeout_seconds - policy.soft_timeout_seconds < reserve_seconds:
146
+ raise LedgerError("task budget does not reserve the full required-completion tail")
147
+ return value
148
+
149
+
150
+ def _validate_ledger_identity(plan: dict[str, Any], ledger: dict[str, Any]) -> TaskBudgetPolicy:
151
+ policy = _task_policy(plan)
152
+ if ledger.get("policy_fingerprint") != _policy_fingerprint(policy):
153
+ raise LedgerError("task phase plan/policy mismatch; refusing to use a different budget plan")
154
+ return policy
155
+
156
+
157
+ def phase_decision(plan_value: Any, ledger_value: Any, phase: str, now: Any) -> dict[str, Any]:
158
+ if phase not in PHASES:
159
+ raise LedgerError(f"invalid task phase: {phase}")
160
+ plan = _plan(plan_value)
161
+ if type(ledger_value) is not dict:
162
+ raise LedgerError("ledger status must be an object")
163
+ policy = _validate_ledger_identity(plan, ledger_value)
164
+ current_now = _strict_number(now, "now")
165
+ started_at = _strict_number(ledger_value.get("started_at"), "started_at")
166
+ soft_deadline = _strict_number(ledger_value.get("soft_deadline"), "soft_deadline")
167
+ hard_deadline = _strict_number(ledger_value.get("hard_deadline"), "hard_deadline")
168
+ if soft_deadline != started_at + policy.soft_timeout_seconds:
169
+ raise LedgerError("task phase soft deadline does not match ExecutionPlan task budget")
170
+ if hard_deadline != started_at + policy.hard_timeout_seconds:
171
+ raise LedgerError("task phase hard deadline does not match ExecutionPlan task budget")
172
+ closed = ledger_value.get("closed")
173
+ if type(closed) is not bool:
174
+ raise LedgerError("ledger closed must be boolean")
175
+
176
+ review_hard, parent_finalization, reserve_seconds = _completion_reserve(plan, policy)
177
+ hard_open = (not closed) and current_now < hard_deadline
178
+ permits_general_work = hard_open and current_now < soft_deadline
179
+ permits_required_completion = hard_open and reserve_seconds > 0
180
+ review_deadline = hard_deadline - parent_finalization if reserve_seconds > 0 else None
181
+ permits_review_start = permits_required_completion and review_deadline is not None and current_now < review_deadline
182
+ permits_parent_finalization = hard_open
183
+
184
+ if not hard_open:
185
+ action = "stop"
186
+ elif phase == "required_completion":
187
+ if not permits_required_completion:
188
+ action = "stop"
189
+ elif permits_review_start:
190
+ action = "complete_required"
191
+ else:
192
+ action = "finalize_parent"
193
+ elif permits_general_work:
194
+ action = "continue"
195
+ elif reserve_seconds > 0:
196
+ action = "converge_for_required_completion"
197
+ else:
198
+ action = "converge"
199
+
200
+ return {
201
+ "phase": phase,
202
+ "action": action,
203
+ "permits_phase_start": permits_required_completion if phase == "required_completion" else permits_general_work,
204
+ "permits_general_work": permits_general_work,
205
+ "permits_required_completion": permits_required_completion,
206
+ "permits_review_start": permits_review_start,
207
+ "permits_parent_finalization": permits_parent_finalization,
208
+ "reviewer_reserve_seconds": review_hard,
209
+ "parent_finalization_reserve_seconds": parent_finalization,
210
+ "required_completion_reserve_seconds": reserve_seconds,
211
+ "general_work_deadline": soft_deadline,
212
+ "review_deadline": review_deadline,
213
+ "soft_deadline": soft_deadline,
214
+ "hard_deadline": hard_deadline,
215
+ "remaining_general_work_seconds": max(0.0, soft_deadline - current_now),
216
+ "remaining_review_seconds": 0.0 if review_deadline is None else max(0.0, review_deadline - current_now),
217
+ "remaining_hard_seconds": max(0.0, hard_deadline - current_now),
218
+ "checkpoint_convergence_required": phase == "implementation" and hard_open and current_now >= soft_deadline,
219
+ "required_completion_handoff": reserve_seconds > 0 and hard_open and current_now >= soft_deadline,
220
+ "task_budget": asdict(policy),
221
+ }
222
+
223
+
224
+ def init(state_file: str, task_id: str, plan: dict[str, Any], now: Any) -> dict[str, Any]:
225
+ plan = _plan(plan)
226
+ policy = _task_policy(plan)
227
+ result = init_ledger(state_file, task_id, policy, now)
228
+ result.update(phase_decision(plan, result, "exploration", now))
229
+ return result
230
+
231
+
232
+ def status(state_file: str, task_id: str, plan: dict[str, Any], phase: str, now: Any) -> dict[str, Any]:
233
+ plan = _plan(plan)
234
+ ledger = ledger_status(state_file, task_id, now)
235
+ result = dict(ledger)
236
+ result.update(phase_decision(plan, ledger, phase, now))
237
+ return result
238
+
239
+
240
+ def reserve_phase(
241
+ state_file: str,
242
+ task_id: str,
243
+ plan: dict[str, Any],
244
+ phase: str,
245
+ kind: str,
246
+ reservation_id: str,
247
+ fingerprint: str,
248
+ now: Any,
249
+ ) -> dict[str, Any]:
250
+ plan = _plan(plan)
251
+ if phase not in PHASES:
252
+ raise LedgerError(f"invalid task phase: {phase}")
253
+ expected_phase = RESERVATION_PHASE.get(kind)
254
+ if expected_phase is None:
255
+ raise LedgerError(f"unsupported task phase reservation kind: {kind}")
256
+ if phase != expected_phase:
257
+ raise LedgerError(f"{kind} reservations require phase={expected_phase}")
258
+ if phase == "implementation" and kind not in GENERAL_RESERVATION_KINDS:
259
+ raise LedgerError("implementation phase only accepts general-work reservations")
260
+
261
+ if kind == "review_attempt":
262
+ decision = status(state_file, task_id, plan, phase, now)
263
+ review_deadline = decision["review_deadline"]
264
+ if review_deadline is None:
265
+ raise LedgerError("review_attempt requires a planned review deadline")
266
+ result = reserve(
267
+ state_file,
268
+ task_id,
269
+ kind,
270
+ reservation_id,
271
+ fingerprint,
272
+ now,
273
+ new_reservation_deadline=review_deadline,
274
+ )
275
+ else:
276
+ result = reserve(state_file, task_id, kind, reservation_id, fingerprint, now)
277
+ result.update(phase_decision(plan, result, phase, now))
278
+ return result
279
+
280
+
281
+ def _json(raw: str, label: str) -> Any:
282
+ try:
283
+ return json.loads(raw)
284
+ except json.JSONDecodeError as exc:
285
+ raise LedgerError(f"invalid {label} JSON: {exc.msg}") from None
286
+
287
+
288
+ def _common(parser: argparse.ArgumentParser) -> None:
289
+ parser.add_argument("--state-file", required=True)
290
+ parser.add_argument("--task-id", required=True)
291
+ parser.add_argument("--plan-json", required=True)
292
+ parser.add_argument("--now", required=True)
293
+
294
+
295
+ def _parser() -> argparse.ArgumentParser:
296
+ parser = argparse.ArgumentParser(prog="task_phase_runtime.py", description="phase-aware admission for FlowPilot task budgets")
297
+ commands = parser.add_subparsers(dest="command", required=True)
298
+ init_cmd = commands.add_parser("init")
299
+ _common(init_cmd)
300
+ status_cmd = commands.add_parser("status")
301
+ _common(status_cmd)
302
+ status_cmd.add_argument("--phase", choices=PHASES, required=True)
303
+ reserve_cmd = commands.add_parser("reserve")
304
+ _common(reserve_cmd)
305
+ reserve_cmd.add_argument("--phase", choices=PHASES, required=True)
306
+ reserve_cmd.add_argument("--kind", choices=tuple(RESERVATION_PHASE), required=True)
307
+ reserve_cmd.add_argument("--reservation-id", required=True)
308
+ reserve_cmd.add_argument("--fingerprint", required=True)
309
+ return parser
310
+
311
+
312
+ def main(argv: list[str] | None = None) -> int:
313
+ parser = _parser()
314
+ ns = parser.parse_args(argv)
315
+ try:
316
+ plan = _plan(_json(ns.plan_json, "plan"))
317
+ current_now = _cli_now(ns.now)
318
+ if ns.command == "init":
319
+ result = init(ns.state_file, ns.task_id, plan, current_now)
320
+ elif ns.command == "status":
321
+ result = status(ns.state_file, ns.task_id, plan, ns.phase, current_now)
322
+ else:
323
+ result = reserve_phase(
324
+ ns.state_file,
325
+ ns.task_id,
326
+ plan,
327
+ ns.phase,
328
+ ns.kind,
329
+ ns.reservation_id,
330
+ ns.fingerprint,
331
+ current_now,
332
+ )
333
+ except LedgerError as exc:
334
+ print(str(exc), file=sys.stderr)
335
+ return 2
336
+ print(json.dumps(result, ensure_ascii=False, sort_keys=True))
337
+ return 0
338
+
339
+
340
+ if __name__ == "__main__":
341
+ raise SystemExit(main())