codex-autorunner 0.1.2__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codex_autorunner/__init__.py +12 -1
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/constants.py +3 -0
- codex_autorunner/agents/opencode/harness.py +6 -1
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +176 -47
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +155 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +22 -37
- codex_autorunner/cli.py +5 -1156
- codex_autorunner/codex_cli.py +20 -84
- codex_autorunner/core/__init__.py +4 -0
- codex_autorunner/core/about_car.py +49 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_ids.py +59 -0
- codex_autorunner/core/app_server_logging.py +7 -3
- codex_autorunner/core/app_server_prompts.py +27 -260
- codex_autorunner/core/app_server_threads.py +26 -28
- codex_autorunner/core/app_server_utils.py +165 -0
- codex_autorunner/core/archive.py +349 -0
- codex_autorunner/core/codex_runner.py +12 -2
- codex_autorunner/core/config.py +587 -103
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +136 -0
- codex_autorunner/core/engine.py +1531 -866
- codex_autorunner/core/exceptions.py +4 -0
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +202 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +88 -0
- codex_autorunner/core/flows/reasons.py +52 -0
- codex_autorunner/core/flows/reconciler.py +131 -0
- codex_autorunner/core/flows/runtime.py +382 -0
- codex_autorunner/core/flows/store.py +568 -0
- codex_autorunner/core/flows/transition.py +138 -0
- codex_autorunner/core/flows/ux_helpers.py +257 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/git_utils.py +62 -0
- codex_autorunner/core/hub.py +136 -16
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/notifications.py +14 -2
- codex_autorunner/core/ports/__init__.py +28 -0
- codex_autorunner/core/ports/agent_backend.py +150 -0
- codex_autorunner/core/ports/backend_orchestrator.py +41 -0
- codex_autorunner/core/ports/run_event.py +91 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/review_context.py +5 -8
- codex_autorunner/core/run_index.py +6 -0
- codex_autorunner/core/runner_process.py +5 -2
- codex_autorunner/core/state.py +0 -88
- codex_autorunner/core/state_roots.py +57 -0
- codex_autorunner/core/supervisor_protocol.py +15 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/text_delta_coalescer.py +54 -0
- codex_autorunner/core/ticket_linter_cli.py +201 -0
- codex_autorunner/core/ticket_manager_cli.py +432 -0
- codex_autorunner/core/update.py +24 -16
- codex_autorunner/core/update_paths.py +28 -0
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/usage.py +164 -12
- codex_autorunner/core/utils.py +120 -11
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/review/__init__.py +17 -0
- codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +98 -0
- codex_autorunner/integrations/agents/__init__.py +17 -0
- codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
- codex_autorunner/integrations/agents/codex_adapter.py +90 -0
- codex_autorunner/integrations/agents/codex_backend.py +448 -0
- codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
- codex_autorunner/integrations/agents/opencode_backend.py +598 -0
- codex_autorunner/integrations/agents/runner.py +91 -0
- codex_autorunner/integrations/agents/wiring.py +271 -0
- codex_autorunner/integrations/app_server/client.py +583 -152
- codex_autorunner/integrations/app_server/env.py +2 -107
- codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +204 -165
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +221 -0
- codex_autorunner/integrations/telegram/constants.py +17 -2
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -4
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
- codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +1364 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +137 -478
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +17 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +121 -9
- codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
- codex_autorunner/integrations/telegram/helpers.py +111 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +221 -42
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +611 -0
- codex_autorunner/integrations/telegram/transport.py +39 -4
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +37 -67
- codex_autorunner/routes/agents.py +2 -137
- codex_autorunner/routes/analytics.py +3 -0
- codex_autorunner/routes/app_server.py +2 -131
- codex_autorunner/routes/base.py +2 -624
- codex_autorunner/routes/file_chat.py +7 -0
- codex_autorunner/routes/flows.py +7 -0
- codex_autorunner/routes/messages.py +7 -0
- codex_autorunner/routes/repos.py +2 -196
- codex_autorunner/routes/review.py +2 -147
- codex_autorunner/routes/sessions.py +2 -175
- codex_autorunner/routes/settings.py +2 -168
- codex_autorunner/routes/shared.py +2 -275
- codex_autorunner/routes/system.py +4 -188
- codex_autorunner/routes/usage.py +3 -0
- codex_autorunner/routes/voice.py +2 -119
- codex_autorunner/routes/workspace.py +3 -0
- codex_autorunner/server.py +3 -2
- codex_autorunner/static/agentControls.js +41 -11
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +35 -24
- codex_autorunner/static/archive.js +826 -0
- codex_autorunner/static/archiveApi.js +37 -0
- codex_autorunner/static/autoRefresh.js +36 -8
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +344 -325
- codex_autorunner/static/diffRenderer.js +37 -0
- codex_autorunner/static/docChatCore.js +324 -0
- codex_autorunner/static/docChatStorage.js +65 -0
- codex_autorunner/static/docChatVoice.js +65 -0
- codex_autorunner/static/docEditor.js +133 -0
- codex_autorunner/static/env.js +1 -0
- codex_autorunner/static/eventSummarizer.js +166 -0
- codex_autorunner/static/fileChat.js +182 -0
- codex_autorunner/static/health.js +155 -0
- codex_autorunner/static/hub.js +126 -185
- codex_autorunner/static/index.html +839 -863
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +873 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/preserve.js +17 -0
- codex_autorunner/static/settings.js +149 -217
- codex_autorunner/static/smartRefresh.js +52 -0
- codex_autorunner/static/styles.css +8850 -3876
- codex_autorunner/static/tabs.js +175 -11
- codex_autorunner/static/terminal.js +32 -0
- codex_autorunner/static/terminalManager.js +34 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +844 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1988 -0
- codex_autorunner/static/utils.js +43 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +765 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/surfaces/__init__.py +5 -0
- codex_autorunner/surfaces/cli/__init__.py +6 -0
- codex_autorunner/surfaces/cli/cli.py +1224 -0
- codex_autorunner/surfaces/cli/codex_cli.py +20 -0
- codex_autorunner/surfaces/telegram/__init__.py +3 -0
- codex_autorunner/surfaces/web/__init__.py +1 -0
- codex_autorunner/surfaces/web/app.py +2019 -0
- codex_autorunner/surfaces/web/hub_jobs.py +192 -0
- codex_autorunner/surfaces/web/middleware.py +587 -0
- codex_autorunner/surfaces/web/pty_session.py +370 -0
- codex_autorunner/surfaces/web/review.py +6 -0
- codex_autorunner/surfaces/web/routes/__init__.py +78 -0
- codex_autorunner/surfaces/web/routes/agents.py +138 -0
- codex_autorunner/surfaces/web/routes/analytics.py +277 -0
- codex_autorunner/surfaces/web/routes/app_server.py +132 -0
- codex_autorunner/surfaces/web/routes/archive.py +357 -0
- codex_autorunner/surfaces/web/routes/base.py +615 -0
- codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
- codex_autorunner/surfaces/web/routes/flows.py +1164 -0
- codex_autorunner/surfaces/web/routes/messages.py +459 -0
- codex_autorunner/surfaces/web/routes/repos.py +197 -0
- codex_autorunner/surfaces/web/routes/review.py +148 -0
- codex_autorunner/surfaces/web/routes/sessions.py +176 -0
- codex_autorunner/surfaces/web/routes/settings.py +169 -0
- codex_autorunner/surfaces/web/routes/shared.py +280 -0
- codex_autorunner/surfaces/web/routes/system.py +196 -0
- codex_autorunner/surfaces/web/routes/usage.py +89 -0
- codex_autorunner/surfaces/web/routes/voice.py +120 -0
- codex_autorunner/surfaces/web/routes/workspace.py +271 -0
- codex_autorunner/surfaces/web/runner_manager.py +25 -0
- codex_autorunner/surfaces/web/schemas.py +417 -0
- codex_autorunner/surfaces/web/static_assets.py +490 -0
- codex_autorunner/surfaces/web/static_refresh.py +86 -0
- codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
- codex_autorunner/tickets/__init__.py +27 -0
- codex_autorunner/tickets/agent_pool.py +399 -0
- codex_autorunner/tickets/files.py +89 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +97 -0
- codex_autorunner/tickets/outbox.py +244 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +881 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/__init__.py +5 -1
- codex_autorunner/web/app.py +2 -1771
- codex_autorunner/web/hub_jobs.py +2 -191
- codex_autorunner/web/middleware.py +2 -587
- codex_autorunner/web/pty_session.py +2 -369
- codex_autorunner/web/runner_manager.py +2 -24
- codex_autorunner/web/schemas.py +2 -396
- codex_autorunner/web/static_assets.py +4 -484
- codex_autorunner/web/static_refresh.py +2 -85
- codex_autorunner/web/terminal_sessions.py +2 -77
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +335 -0
- codex_autorunner-1.1.0.dist-info/METADATA +154 -0
- codex_autorunner-1.1.0.dist-info/RECORD +308 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +1 -1
- codex_autorunner/agents/execution/policy.py +0 -292
- codex_autorunner/agents/factory.py +0 -52
- codex_autorunner/agents/orchestrator.py +0 -358
- codex_autorunner/core/doc_chat.py +0 -1446
- codex_autorunner/core/snapshot.py +0 -580
- codex_autorunner/integrations/github/chatops.py +0 -268
- codex_autorunner/integrations/github/pr_flow.py +0 -1314
- codex_autorunner/routes/docs.py +0 -381
- codex_autorunner/routes/github.py +0 -327
- codex_autorunner/routes/runs.py +0 -250
- codex_autorunner/spec_ingest.py +0 -812
- codex_autorunner/static/docChatActions.js +0 -287
- codex_autorunner/static/docChatEvents.js +0 -300
- codex_autorunner/static/docChatRender.js +0 -205
- codex_autorunner/static/docChatStream.js +0 -361
- codex_autorunner/static/docs.js +0 -20
- codex_autorunner/static/docsClipboard.js +0 -69
- codex_autorunner/static/docsCrud.js +0 -257
- codex_autorunner/static/docsDocUpdates.js +0 -62
- codex_autorunner/static/docsDrafts.js +0 -16
- codex_autorunner/static/docsElements.js +0 -69
- codex_autorunner/static/docsInit.js +0 -285
- codex_autorunner/static/docsParse.js +0 -160
- codex_autorunner/static/docsSnapshot.js +0 -87
- codex_autorunner/static/docsSpecIngest.js +0 -263
- codex_autorunner/static/docsState.js +0 -127
- codex_autorunner/static/docsThreadRegistry.js +0 -44
- codex_autorunner/static/docsUi.js +0 -153
- codex_autorunner/static/docsVoice.js +0 -56
- codex_autorunner/static/github.js +0 -504
- codex_autorunner/static/logs.js +0 -678
- codex_autorunner/static/review.js +0 -157
- codex_autorunner/static/runs.js +0 -418
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -94
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.2.dist-info/METADATA +0 -249
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
"""Portable ticket management CLI (self-contained, no repo imports)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
MANAGER_BASENAME = "ticket_tool.py"
|
|
8
|
+
MANAGER_REL_PATH = Path(".codex-autorunner/bin") / MANAGER_BASENAME
|
|
9
|
+
|
|
10
|
+
_SCRIPT = """#!/usr/bin/env python3
|
|
11
|
+
\"\"\"Manage Codex Autorunner tickets (list, insert, move, create, lint).
|
|
12
|
+
|
|
13
|
+
Commands:
|
|
14
|
+
list Show ticket order with titles/done flags.
|
|
15
|
+
lint Validate ticket filenames and frontmatter.
|
|
16
|
+
insert --before N Shift tickets >= N up by COUNT (default 1).
|
|
17
|
+
insert --after N Shift tickets > N up by COUNT (default 1).
|
|
18
|
+
move --start A --to B Move ticket/block starting at A (or A..END)
|
|
19
|
+
so it begins at position B (1-indexed).
|
|
20
|
+
create --title \"...\" Create a new ticket at the next or specified
|
|
21
|
+
index. Use --at to place into a gap.
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
ticket_tool.py list
|
|
25
|
+
ticket_tool.py insert --before 3
|
|
26
|
+
ticket_tool.py create --title \"Investigate flaky test\" --at 3
|
|
27
|
+
ticket_tool.py move --start 5 --end 7 --to 2
|
|
28
|
+
ticket_tool.py lint
|
|
29
|
+
|
|
30
|
+
Notes:
|
|
31
|
+
- Filenames must match TICKET-<number>[suffix].md.
|
|
32
|
+
- PyYAML is required (pip install pyyaml) for linting/title extraction.
|
|
33
|
+
- The tool is intentionally dependency-light and safe to run from any
|
|
34
|
+
virtualenv (or none).
|
|
35
|
+
\"\"\"
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import argparse
|
|
40
|
+
import re
|
|
41
|
+
import sys
|
|
42
|
+
from dataclasses import dataclass
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
from typing import List, Optional, Sequence, Tuple
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
import yaml # type: ignore
|
|
48
|
+
except ImportError: # pragma: no cover
|
|
49
|
+
yaml = None
|
|
50
|
+
|
|
51
|
+
_TICKET_NAME_RE = re.compile(r"^TICKET-(\\d{3,})([^/]*)\\.md$", re.IGNORECASE)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class TicketFile:
|
|
56
|
+
index: int
|
|
57
|
+
path: Path
|
|
58
|
+
suffix: str
|
|
59
|
+
title: Optional[str]
|
|
60
|
+
done: Optional[bool]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _ticket_dir(repo_root: Path) -> Path:
|
|
64
|
+
return repo_root / ".codex-autorunner" / "tickets"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _ticket_paths(ticket_dir: Path) -> Tuple[List[Path], List[str]]:
|
|
68
|
+
tickets: List[tuple[int, Path, str]] = []
|
|
69
|
+
errors: List[str] = []
|
|
70
|
+
for path in sorted(ticket_dir.iterdir()):
|
|
71
|
+
if not path.is_file():
|
|
72
|
+
continue
|
|
73
|
+
m = _TICKET_NAME_RE.match(path.name)
|
|
74
|
+
if not m:
|
|
75
|
+
errors.append(
|
|
76
|
+
f\"{path}: Invalid ticket filename; expected TICKET-<number>[suffix].md\"
|
|
77
|
+
)
|
|
78
|
+
continue
|
|
79
|
+
try:
|
|
80
|
+
idx = int(m.group(1))
|
|
81
|
+
except ValueError:
|
|
82
|
+
errors.append(f\"{path}: Invalid ticket filename; number must be digits\")
|
|
83
|
+
continue
|
|
84
|
+
tickets.append((idx, path, m.group(2)))
|
|
85
|
+
tickets.sort(key=lambda t: t[0])
|
|
86
|
+
return [p for _, p, _ in tickets], errors
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _split_frontmatter(text: str):
|
|
90
|
+
if not text or not text.lstrip().startswith(\"---\"):
|
|
91
|
+
return None, [\"Missing YAML frontmatter (expected leading '---').\"]
|
|
92
|
+
lines = text.splitlines()
|
|
93
|
+
end_idx = None
|
|
94
|
+
for idx in range(1, len(lines)):
|
|
95
|
+
if lines[idx].strip() in (\"---\", \"...\"):
|
|
96
|
+
end_idx = idx
|
|
97
|
+
break
|
|
98
|
+
if end_idx is None:
|
|
99
|
+
return None, [\"Frontmatter is not closed (missing trailing '---').\"]
|
|
100
|
+
fm_yaml = \"\\n\".join(lines[1:end_idx])
|
|
101
|
+
return fm_yaml, []
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _parse_yaml(fm_yaml: Optional[str]):
|
|
105
|
+
if fm_yaml is None:
|
|
106
|
+
return {}, [\"Missing or invalid YAML frontmatter (expected a mapping).\"]
|
|
107
|
+
if yaml is None:
|
|
108
|
+
return {}, [
|
|
109
|
+
\"PyYAML is required to lint tickets. Install with: python3 -m pip install --user pyyaml\"
|
|
110
|
+
]
|
|
111
|
+
try:
|
|
112
|
+
loaded = yaml.safe_load(fm_yaml)
|
|
113
|
+
except Exception as exc: # noqa: BLE001
|
|
114
|
+
return {}, [f\"YAML parse error: {exc}\"]
|
|
115
|
+
if loaded is None or not isinstance(loaded, dict):
|
|
116
|
+
return {}, [\"Invalid YAML frontmatter (expected a mapping).\"]
|
|
117
|
+
return loaded, []
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _lint_frontmatter(data: dict):
|
|
121
|
+
errors: List[str] = []
|
|
122
|
+
agent = data.get(\"agent\")
|
|
123
|
+
if not isinstance(agent, str) or not agent.strip():
|
|
124
|
+
errors.append(\"frontmatter.agent is required and must be a non-empty string.\")
|
|
125
|
+
done = data.get(\"done\")
|
|
126
|
+
if not isinstance(done, bool):
|
|
127
|
+
errors.append(\"frontmatter.done is required and must be a boolean.\")
|
|
128
|
+
return errors
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _read_ticket(path: Path) -> Tuple[Optional[TicketFile], List[str]]:
|
|
132
|
+
try:
|
|
133
|
+
raw = path.read_text(encoding=\"utf-8\")
|
|
134
|
+
except OSError as exc:
|
|
135
|
+
return None, [f\"{path}: Unable to read file ({exc}).\"]
|
|
136
|
+
|
|
137
|
+
fm_yaml, fm_errors = _split_frontmatter(raw)
|
|
138
|
+
if fm_errors:
|
|
139
|
+
return None, [f\"{path}: {msg}\" for msg in fm_errors]
|
|
140
|
+
|
|
141
|
+
data, parse_errors = _parse_yaml(fm_yaml)
|
|
142
|
+
if parse_errors:
|
|
143
|
+
return None, [f\"{path}: {msg}\" for msg in parse_errors]
|
|
144
|
+
|
|
145
|
+
lint_errors = _lint_frontmatter(data)
|
|
146
|
+
if lint_errors:
|
|
147
|
+
return None, [f\"{path}: {msg}\" for msg in lint_errors]
|
|
148
|
+
|
|
149
|
+
title = data.get(\"title\") if isinstance(data, dict) else None
|
|
150
|
+
done_val = data.get(\"done\") if isinstance(data, dict) else None
|
|
151
|
+
|
|
152
|
+
m = _TICKET_NAME_RE.match(path.name)
|
|
153
|
+
idx = int(m.group(1)) if m else 0
|
|
154
|
+
suffix = m.group(2) if m else \"\"
|
|
155
|
+
return TicketFile(index=idx, path=path, suffix=suffix, title=title, done=done_val), []
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _ticket_files(ticket_dir: Path) -> Tuple[List[TicketFile], List[str]]:
|
|
159
|
+
paths, name_errors = _ticket_paths(ticket_dir)
|
|
160
|
+
tickets: List[TicketFile] = []
|
|
161
|
+
errors = list(name_errors)
|
|
162
|
+
for path in paths:
|
|
163
|
+
ticket, errs = _read_ticket(path)
|
|
164
|
+
if ticket:
|
|
165
|
+
tickets.append(ticket)
|
|
166
|
+
errors.extend(errs)
|
|
167
|
+
tickets.sort(key=lambda t: t.index)
|
|
168
|
+
return tickets, errors
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _pad_width(indices: Sequence[int]) -> int:
|
|
172
|
+
if not indices:
|
|
173
|
+
return 3
|
|
174
|
+
return max(3, max(len(str(i)) for i in indices))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _fmt_name(index: int, suffix: str, width: int) -> str:
|
|
178
|
+
return f\"TICKET-{index:0{width}d}{suffix}.md\"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _safe_renames(mapping: Sequence[tuple[Path, Path]]) -> None:
|
|
182
|
+
temp_pairs: list[tuple[Path, Path]] = []
|
|
183
|
+
for src, dst in mapping:
|
|
184
|
+
if src == dst:
|
|
185
|
+
continue
|
|
186
|
+
temp = src.with_name(src.name + \".tmp-move\")
|
|
187
|
+
counter = 0
|
|
188
|
+
while temp.exists():
|
|
189
|
+
counter += 1
|
|
190
|
+
temp = src.with_name(f\"{src.name}.tmp-move-{counter}\")
|
|
191
|
+
src.rename(temp)
|
|
192
|
+
temp_pairs.append((temp, dst))
|
|
193
|
+
|
|
194
|
+
for temp, dst in temp_pairs:
|
|
195
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
196
|
+
temp.rename(dst)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def cmd_list(ticket_dir: Path) -> int:
|
|
200
|
+
tickets, errors = _ticket_files(ticket_dir)
|
|
201
|
+
if errors:
|
|
202
|
+
for msg in errors:
|
|
203
|
+
sys.stderr.write(msg + \"\\n\")
|
|
204
|
+
width = _pad_width([t.index for t in tickets])
|
|
205
|
+
for t in tickets:
|
|
206
|
+
status = \"done\" if t.done else \"open\"
|
|
207
|
+
title = f\" - {t.title}\" if t.title else \"\"
|
|
208
|
+
sys.stdout.write(f\"{t.index:0{width}d} [{status}] {t.path.name}{title}\\n\")
|
|
209
|
+
if errors:
|
|
210
|
+
return 1
|
|
211
|
+
return 0
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def cmd_lint(ticket_dir: Path) -> int:
|
|
215
|
+
paths, name_errors = _ticket_paths(ticket_dir)
|
|
216
|
+
errors = list(name_errors)
|
|
217
|
+
for path in paths:
|
|
218
|
+
_, errs = _read_ticket(path)
|
|
219
|
+
errors.extend(errs)
|
|
220
|
+
|
|
221
|
+
if errors:
|
|
222
|
+
for msg in errors:
|
|
223
|
+
sys.stderr.write(msg + \"\\n\")
|
|
224
|
+
return 1
|
|
225
|
+
sys.stdout.write(f\"OK: {len(paths)} ticket(s) linted.\\n\")
|
|
226
|
+
return 0
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _shift(ticket_dir: Path, start_idx: int, delta: int) -> None:
|
|
230
|
+
if delta == 0:
|
|
231
|
+
return
|
|
232
|
+
paths, errors = _ticket_paths(ticket_dir)
|
|
233
|
+
if errors:
|
|
234
|
+
raise ValueError(\"Cannot shift while filenames are invalid; run lint first.\")
|
|
235
|
+
iterable = reversed(paths) if delta > 0 else paths
|
|
236
|
+
width = _pad_width([_parse_index(p.name) for p in paths] + [start_idx + delta])
|
|
237
|
+
mapping: list[tuple[Path, Path]] = []
|
|
238
|
+
for path in iterable:
|
|
239
|
+
idx = _parse_index(path.name)
|
|
240
|
+
if idx is None or idx < start_idx:
|
|
241
|
+
continue
|
|
242
|
+
new_idx = idx + delta
|
|
243
|
+
if new_idx <= 0:
|
|
244
|
+
raise ValueError(\"Shift would create non-positive ticket index\")
|
|
245
|
+
suffix = _parse_suffix(path.name)
|
|
246
|
+
target = path.with_name(_fmt_name(new_idx, suffix, width))
|
|
247
|
+
mapping.append((path, target))
|
|
248
|
+
_safe_renames(mapping)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _parse_index(name: str) -> Optional[int]:
|
|
252
|
+
m = _TICKET_NAME_RE.match(name)
|
|
253
|
+
return int(m.group(1)) if m else None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _parse_suffix(name: str) -> str:
|
|
257
|
+
m = _TICKET_NAME_RE.match(name)
|
|
258
|
+
return m.group(2) if m else \"\"
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def cmd_insert(ticket_dir: Path, *, before: Optional[int], after: Optional[int], count: int) -> int:
|
|
262
|
+
if (before is None) == (after is None):
|
|
263
|
+
sys.stderr.write(\"Specify exactly one of --before or --after.\\n\")
|
|
264
|
+
return 2
|
|
265
|
+
anchor = before if before is not None else after + 1 # type: ignore[operator]
|
|
266
|
+
if anchor is None or anchor < 1:
|
|
267
|
+
sys.stderr.write(\"Anchor index must be >= 1.\\n\")
|
|
268
|
+
return 2
|
|
269
|
+
try:
|
|
270
|
+
_shift(ticket_dir, anchor, count)
|
|
271
|
+
except ValueError as exc:
|
|
272
|
+
sys.stderr.write(str(exc) + \"\\n\")
|
|
273
|
+
return 1
|
|
274
|
+
return 0
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _yaml_scalar(value: str) -> str:
|
|
278
|
+
'''Render a Python string as a safe single-line YAML scalar.
|
|
279
|
+
|
|
280
|
+
Returns a double-quoted value with backslashes, quotes, and newlines escaped.
|
|
281
|
+
'''
|
|
282
|
+
|
|
283
|
+
escaped = (
|
|
284
|
+
value.replace("\\\\", "\\\\\\\\")
|
|
285
|
+
.replace('"', '\\\\\"')
|
|
286
|
+
.replace("\\n", "\\\\n")
|
|
287
|
+
)
|
|
288
|
+
return f'"{escaped}"'
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def cmd_create(ticket_dir: Path, *, title: str, agent: str, at: Optional[int]) -> int:
|
|
292
|
+
tickets, errors = _ticket_files(ticket_dir)
|
|
293
|
+
if errors:
|
|
294
|
+
for msg in errors:
|
|
295
|
+
sys.stderr.write(msg + \"\\n\")
|
|
296
|
+
return 1
|
|
297
|
+
existing_indices = [t.index for t in tickets]
|
|
298
|
+
next_index = max(existing_indices) + 1 if existing_indices else 1
|
|
299
|
+
index = at or next_index
|
|
300
|
+
if index in existing_indices:
|
|
301
|
+
sys.stderr.write(
|
|
302
|
+
f\"Ticket index {index} already exists. Use insert to open a gap or choose --at another index.\\n\"
|
|
303
|
+
)
|
|
304
|
+
return 1
|
|
305
|
+
width = _pad_width(existing_indices + [index])
|
|
306
|
+
name = _fmt_name(index, \"\", width)
|
|
307
|
+
path = ticket_dir / name
|
|
308
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
309
|
+
title_scalar = _yaml_scalar(title)
|
|
310
|
+
agent_scalar = _yaml_scalar(agent)
|
|
311
|
+
body = (
|
|
312
|
+
f\"---\\n\"
|
|
313
|
+
f\"title: {title_scalar}\\n\"
|
|
314
|
+
f\"agent: {agent_scalar}\\n\"
|
|
315
|
+
f\"done: false\\n\"
|
|
316
|
+
f\"---\\n\\n\"
|
|
317
|
+
f\"## Goal\\n- \\n\"
|
|
318
|
+
)
|
|
319
|
+
path.write_text(body, encoding=\"utf-8\")
|
|
320
|
+
sys.stdout.write(f\"Created {path}\\n\")
|
|
321
|
+
return 0
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def cmd_move(ticket_dir: Path, *, start: int, end: Optional[int], to: int) -> int:
|
|
325
|
+
if start < 1 or to < 1:
|
|
326
|
+
sys.stderr.write(\"Indices must be >= 1.\\n\")
|
|
327
|
+
return 2
|
|
328
|
+
tickets, errors = _ticket_files(ticket_dir)
|
|
329
|
+
if errors:
|
|
330
|
+
for msg in errors:
|
|
331
|
+
sys.stderr.write(msg + \"\\n\")
|
|
332
|
+
return 1
|
|
333
|
+
indices = [t.index for t in tickets]
|
|
334
|
+
if start not in indices:
|
|
335
|
+
sys.stderr.write(f\"No ticket at index {start}.\\n\")
|
|
336
|
+
return 1
|
|
337
|
+
end_idx = end if end is not None else start
|
|
338
|
+
if end_idx < start:
|
|
339
|
+
sys.stderr.write(\"--end must be >= --start.\\n\")
|
|
340
|
+
return 2
|
|
341
|
+
block = [t for t in tickets if start <= t.index <= end_idx]
|
|
342
|
+
if not block:
|
|
343
|
+
sys.stderr.write(\"No tickets in the specified move range.\\n\")
|
|
344
|
+
return 1
|
|
345
|
+
remaining = [t for t in tickets if t not in block]
|
|
346
|
+
insert_pos = to - 1
|
|
347
|
+
if insert_pos < 0 or insert_pos > len(remaining):
|
|
348
|
+
sys.stderr.write(\"Target position is out of range.\\n\")
|
|
349
|
+
return 1
|
|
350
|
+
new_order = remaining[:insert_pos] + block + remaining[insert_pos:]
|
|
351
|
+
width = _pad_width([t.index for t in new_order])
|
|
352
|
+
|
|
353
|
+
mapping: list[tuple[Path, Path]] = []
|
|
354
|
+
for new_idx, ticket in enumerate(new_order, start=1):
|
|
355
|
+
target = ticket.path.with_name(_fmt_name(new_idx, ticket.suffix, width))
|
|
356
|
+
mapping.append((ticket.path, target))
|
|
357
|
+
_safe_renames(mapping)
|
|
358
|
+
return 0
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
362
|
+
parser = argparse.ArgumentParser(description=\"Manage Codex Autorunner tickets.\")
|
|
363
|
+
sub = parser.add_subparsers(dest=\"cmd\", required=True)
|
|
364
|
+
|
|
365
|
+
sub.add_parser(\"list\", help=\"List tickets in order\")
|
|
366
|
+
sub.add_parser(\"lint\", help=\"Validate ticket filenames and frontmatter\")
|
|
367
|
+
|
|
368
|
+
insert_p = sub.add_parser(\"insert\", help=\"Insert gap by shifting tickets\")
|
|
369
|
+
insert_group = insert_p.add_mutually_exclusive_group(required=True)
|
|
370
|
+
insert_group.add_argument(\"--before\", type=int, help=\"First index to shift upward\")
|
|
371
|
+
insert_group.add_argument(\"--after\", type=int, help=\"Shift tickets after this index\")
|
|
372
|
+
insert_p.add_argument(\"--count\", type=int, default=1, help=\"How many slots to insert (default 1)\")
|
|
373
|
+
|
|
374
|
+
create_p = sub.add_parser(\"create\", help=\"Create a new ticket\")
|
|
375
|
+
create_p.add_argument(\"--title\", required=True, help=\"Ticket title\")
|
|
376
|
+
create_p.add_argument(\"--agent\", default=\"codex\", help=\"Frontmatter agent (default: codex)\")
|
|
377
|
+
create_p.add_argument(
|
|
378
|
+
\"--at\",
|
|
379
|
+
type=int,
|
|
380
|
+
help=\"Index to use (must be unused). Defaults to next available index.\",
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
move_p = sub.add_parser(\"move\", help=\"Move a ticket or block to a new position\")
|
|
384
|
+
move_p.add_argument(\"--start\", type=int, required=True, help=\"First index in the block to move\")
|
|
385
|
+
move_p.add_argument(\"--end\", type=int, help=\"Last index in the block (defaults to start)\")
|
|
386
|
+
move_p.add_argument(\"--to\", type=int, required=True, help=\"Destination position (1-indexed)\")
|
|
387
|
+
|
|
388
|
+
args = parser.parse_args(argv)
|
|
389
|
+
repo_root = Path.cwd()
|
|
390
|
+
ticket_dir = _ticket_dir(repo_root)
|
|
391
|
+
if not ticket_dir.exists():
|
|
392
|
+
sys.stderr.write(f\"Tickets directory not found: {ticket_dir}\\n\")
|
|
393
|
+
return 2
|
|
394
|
+
|
|
395
|
+
if args.cmd == \"list\":
|
|
396
|
+
return cmd_list(ticket_dir)
|
|
397
|
+
if args.cmd == \"lint\":
|
|
398
|
+
return cmd_lint(ticket_dir)
|
|
399
|
+
if args.cmd == \"insert\":
|
|
400
|
+
return cmd_insert(ticket_dir, before=args.before, after=args.after, count=args.count)
|
|
401
|
+
if args.cmd == \"create\":
|
|
402
|
+
return cmd_create(ticket_dir, title=args.title, agent=args.agent, at=args.at)
|
|
403
|
+
if args.cmd == \"move\":
|
|
404
|
+
return cmd_move(ticket_dir, start=args.start, end=args.end, to=args.to)
|
|
405
|
+
parser.error(\"Unknown command\")
|
|
406
|
+
return 2
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
if __name__ == \"__main__\": # pragma: no cover
|
|
410
|
+
sys.exit(main())
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def ensure_ticket_manager(repo_root: Path, *, force: bool = False) -> Path:
|
|
415
|
+
"""Ensure the ticket management CLI exists under .codex-autorunner/bin."""
|
|
416
|
+
|
|
417
|
+
path = repo_root / MANAGER_REL_PATH
|
|
418
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
419
|
+
|
|
420
|
+
existing = None
|
|
421
|
+
if path.exists():
|
|
422
|
+
try:
|
|
423
|
+
existing = path.read_text(encoding="utf-8")
|
|
424
|
+
except OSError:
|
|
425
|
+
existing = None
|
|
426
|
+
|
|
427
|
+
if force or existing != _SCRIPT:
|
|
428
|
+
path.write_text(_SCRIPT, encoding="utf-8")
|
|
429
|
+
mode = path.stat().st_mode
|
|
430
|
+
path.chmod(mode | 0o111)
|
|
431
|
+
|
|
432
|
+
return path
|
codex_autorunner/core/update.py
CHANGED
|
@@ -11,6 +11,7 @@ from typing import Optional
|
|
|
11
11
|
from urllib.parse import unquote, urlparse
|
|
12
12
|
|
|
13
13
|
from .git_utils import GitError, run_git
|
|
14
|
+
from .update_paths import resolve_update_paths
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class UpdateInProgressError(RuntimeError):
|
|
@@ -55,7 +56,7 @@ def _normalize_update_ref(raw: Optional[str]) -> str:
|
|
|
55
56
|
|
|
56
57
|
|
|
57
58
|
def _update_status_path() -> Path:
|
|
58
|
-
return
|
|
59
|
+
return resolve_update_paths().status_path
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
def _write_update_status(status: str, message: str, **extra) -> None:
|
|
@@ -134,7 +135,7 @@ def _read_update_status() -> Optional[dict[str, object]]:
|
|
|
134
135
|
|
|
135
136
|
|
|
136
137
|
def _update_lock_path() -> Path:
|
|
137
|
-
return
|
|
138
|
+
return resolve_update_paths().lock_path
|
|
138
139
|
|
|
139
140
|
|
|
140
141
|
def _read_update_lock() -> Optional[dict[str, object]]:
|
|
@@ -281,9 +282,7 @@ def _system_update_check(
|
|
|
281
282
|
update_cache_dir: Optional[Path] = None,
|
|
282
283
|
) -> dict:
|
|
283
284
|
module_dir = module_dir or Path(__file__).resolve().parent
|
|
284
|
-
update_cache_dir = update_cache_dir or (
|
|
285
|
-
Path.home() / ".codex-autorunner" / "update_cache"
|
|
286
|
-
)
|
|
285
|
+
update_cache_dir = update_cache_dir or resolve_update_paths().cache_dir
|
|
287
286
|
repo_ref = _normalize_update_ref(repo_ref)
|
|
288
287
|
|
|
289
288
|
repo_root = _resolve_local_repo_root(
|
|
@@ -378,6 +377,7 @@ def _system_update_worker(
|
|
|
378
377
|
update_dir: Path,
|
|
379
378
|
logger: logging.Logger,
|
|
380
379
|
update_target: str = "both",
|
|
380
|
+
skip_checks: bool = False,
|
|
381
381
|
) -> None:
|
|
382
382
|
status_path = _update_status_path()
|
|
383
383
|
lock_acquired = False
|
|
@@ -457,10 +457,14 @@ def _system_update_worker(
|
|
|
457
457
|
_run_cmd(["git", "fetch", "origin", repo_ref], cwd=update_dir)
|
|
458
458
|
_run_cmd(["git", "reset", "--hard", "FETCH_HEAD"], cwd=update_dir)
|
|
459
459
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
460
|
+
skip_checks_env = os.environ.get("CODEX_AUTORUNNER_SKIP_UPDATE_CHECKS") == "1"
|
|
461
|
+
if skip_checks_env or skip_checks:
|
|
462
|
+
if skip_checks_env:
|
|
463
|
+
logger.info(
|
|
464
|
+
"Skipping update checks (CODEX_AUTORUNNER_SKIP_UPDATE_CHECKS=1)."
|
|
465
|
+
)
|
|
466
|
+
else:
|
|
467
|
+
logger.info("Skipping update checks (update.skip_checks=true).")
|
|
464
468
|
else:
|
|
465
469
|
logger.info("Running checks...")
|
|
466
470
|
try:
|
|
@@ -526,6 +530,7 @@ def _spawn_update_process(
|
|
|
526
530
|
update_dir: Path,
|
|
527
531
|
logger: logging.Logger,
|
|
528
532
|
update_target: str = "both",
|
|
533
|
+
skip_checks: bool = False,
|
|
529
534
|
notify_chat_id: Optional[int] = None,
|
|
530
535
|
notify_thread_id: Optional[int] = None,
|
|
531
536
|
notify_reply_to: Optional[int] = None,
|
|
@@ -565,14 +570,17 @@ def _spawn_update_process(
|
|
|
565
570
|
"--log-path",
|
|
566
571
|
str(log_path),
|
|
567
572
|
]
|
|
573
|
+
if skip_checks:
|
|
574
|
+
cmd.append("--skip-checks")
|
|
568
575
|
try:
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
+
with log_path.open("a", encoding="utf-8") as log_file:
|
|
577
|
+
subprocess.Popen(
|
|
578
|
+
cmd,
|
|
579
|
+
cwd=str(update_dir.parent),
|
|
580
|
+
start_new_session=True,
|
|
581
|
+
stdout=log_file,
|
|
582
|
+
stderr=log_file,
|
|
583
|
+
)
|
|
576
584
|
except Exception:
|
|
577
585
|
logger.exception("Failed to spawn update worker")
|
|
578
586
|
_write_update_status(
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from .state_roots import resolve_global_state_root
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class UpdatePaths:
|
|
12
|
+
status_path: Path
|
|
13
|
+
lock_path: Path
|
|
14
|
+
cache_dir: Path
|
|
15
|
+
compact_status_path: Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def resolve_update_paths(
|
|
19
|
+
*, config: Optional[Any] = None, repo_root: Optional[Path] = None
|
|
20
|
+
) -> UpdatePaths:
|
|
21
|
+
"""Resolve update status, lock, cache, and compact status paths."""
|
|
22
|
+
root = resolve_global_state_root(config=config, repo_root=repo_root)
|
|
23
|
+
return UpdatePaths(
|
|
24
|
+
status_path=root / "update_status.json",
|
|
25
|
+
lock_path=root / "update.lock",
|
|
26
|
+
cache_dir=root / "update_cache",
|
|
27
|
+
compact_status_path=root / "compact_status.json",
|
|
28
|
+
)
|
|
@@ -23,6 +23,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
23
23
|
parser.add_argument("--update-dir", required=True)
|
|
24
24
|
parser.add_argument("--log-path", required=True)
|
|
25
25
|
parser.add_argument("--target", default="both")
|
|
26
|
+
parser.add_argument("--skip-checks", action="store_true")
|
|
26
27
|
args = parser.parse_args(argv)
|
|
27
28
|
|
|
28
29
|
update_dir = Path(args.update_dir).expanduser()
|
|
@@ -36,6 +37,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
36
37
|
update_dir=update_dir,
|
|
37
38
|
logger=logger,
|
|
38
39
|
update_target=args.target,
|
|
40
|
+
skip_checks=bool(args.skip_checks),
|
|
39
41
|
)
|
|
40
42
|
return 0
|
|
41
43
|
|