mindcode 0.2.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.
- mindcode/__init__.py +5 -0
- mindcode/__main__.py +4 -0
- mindcode/__pycache__/__init__.cpython-313.pyc +0 -0
- mindcode/__pycache__/__main__.cpython-313.pyc +0 -0
- mindcode/__pycache__/_version.cpython-313.pyc +0 -0
- mindcode/__pycache__/config.cpython-313.pyc +0 -0
- mindcode/__pycache__/policy.cpython-313.pyc +0 -0
- mindcode/__pycache__/remote.cpython-313.pyc +0 -0
- mindcode/__pycache__/render.cpython-313.pyc +0 -0
- mindcode/__pycache__/runtime.cpython-313.pyc +0 -0
- mindcode/__pycache__/tasking.cpython-313.pyc +0 -0
- mindcode/__pycache__/terminal_core.cpython-313.pyc +0 -0
- mindcode/_version.py +11 -0
- mindcode/approval.py +105 -0
- mindcode/bench/__init__.py +19 -0
- mindcode/bench/__pycache__/__init__.cpython-313.pyc +0 -0
- mindcode/bench/__pycache__/fake_tools.cpython-313.pyc +0 -0
- mindcode/bench/__pycache__/jsonutil.cpython-313.pyc +0 -0
- mindcode/bench/__pycache__/paths.cpython-313.pyc +0 -0
- mindcode/bench/__pycache__/predictions.cpython-313.pyc +0 -0
- mindcode/bench/__pycache__/report.cpython-313.pyc +0 -0
- mindcode/bench/__pycache__/runner.cpython-313.pyc +0 -0
- mindcode/bench/__pycache__/schema.cpython-313.pyc +0 -0
- mindcode/bench/__pycache__/trace.cpython-313.pyc +0 -0
- mindcode/bench/__pycache__/workspace.cpython-313.pyc +0 -0
- mindcode/bench/adapters/__init__.py +22 -0
- mindcode/bench/adapters/__pycache__/__init__.cpython-313.pyc +0 -0
- mindcode/bench/adapters/__pycache__/base.cpython-313.pyc +0 -0
- mindcode/bench/adapters/__pycache__/mini_bfcl.cpython-313.pyc +0 -0
- mindcode/bench/adapters/__pycache__/mini_gaia.cpython-313.pyc +0 -0
- mindcode/bench/adapters/__pycache__/mini_terminal.cpython-313.pyc +0 -0
- mindcode/bench/adapters/__pycache__/swe_bench.cpython-313.pyc +0 -0
- mindcode/bench/adapters/__pycache__/terminal_bench.cpython-313.pyc +0 -0
- mindcode/bench/adapters/base.py +45 -0
- mindcode/bench/adapters/mini_bfcl.py +18 -0
- mindcode/bench/adapters/mini_gaia.py +18 -0
- mindcode/bench/adapters/mini_terminal.py +18 -0
- mindcode/bench/adapters/swe_bench.py +86 -0
- mindcode/bench/adapters/terminal_bench.py +107 -0
- mindcode/bench/fake_tools.py +73 -0
- mindcode/bench/jsonutil.py +20 -0
- mindcode/bench/paths.py +10 -0
- mindcode/bench/predictions.py +65 -0
- mindcode/bench/report.py +48 -0
- mindcode/bench/runner.py +250 -0
- mindcode/bench/schema.py +78 -0
- mindcode/bench/scorers/__pycache__/base.cpython-313.pyc +0 -0
- mindcode/bench/scorers/__pycache__/composite.cpython-313.pyc +0 -0
- mindcode/bench/scorers/__pycache__/exact.cpython-313.pyc +0 -0
- mindcode/bench/scorers/__pycache__/json_call.cpython-313.pyc +0 -0
- mindcode/bench/scorers/__pycache__/swe.cpython-313.pyc +0 -0
- mindcode/bench/scorers/__pycache__/terminal.cpython-313.pyc +0 -0
- mindcode/bench/scorers/__pycache__/terminal_bench.cpython-313.pyc +0 -0
- mindcode/bench/scorers/base.py +22 -0
- mindcode/bench/scorers/composite.py +57 -0
- mindcode/bench/scorers/exact.py +30 -0
- mindcode/bench/scorers/json_call.py +44 -0
- mindcode/bench/scorers/swe.py +29 -0
- mindcode/bench/scorers/terminal.py +43 -0
- mindcode/bench/scorers/terminal_bench.py +25 -0
- mindcode/bench/trace.py +34 -0
- mindcode/bench/workspace.py +127 -0
- mindcode/cli/__init__.py +112 -0
- mindcode/cli/__pycache__/__init__.cpython-313.pyc +0 -0
- mindcode/cli/__pycache__/_shared.cpython-313.pyc +0 -0
- mindcode/cli/_shared.py +43 -0
- mindcode/cli/commands/__init__.py +1 -0
- mindcode/cli/commands/__pycache__/__init__.cpython-313.pyc +0 -0
- mindcode/cli/commands/__pycache__/bench.cpython-313.pyc +0 -0
- mindcode/cli/commands/__pycache__/chat.cpython-313.pyc +0 -0
- mindcode/cli/commands/__pycache__/config_cmd.cpython-313.pyc +0 -0
- mindcode/cli/commands/__pycache__/remote.cpython-313.pyc +0 -0
- mindcode/cli/commands/__pycache__/shell.cpython-313.pyc +0 -0
- mindcode/cli/commands/__pycache__/status.cpython-313.pyc +0 -0
- mindcode/cli/commands/__pycache__/task.cpython-313.pyc +0 -0
- mindcode/cli/commands/__pycache__/terminal.cpython-313.pyc +0 -0
- mindcode/cli/commands/bench.py +365 -0
- mindcode/cli/commands/chat.py +86 -0
- mindcode/cli/commands/config_cmd.py +197 -0
- mindcode/cli/commands/remote.py +146 -0
- mindcode/cli/commands/shell.py +108 -0
- mindcode/cli/commands/status.py +37 -0
- mindcode/cli/commands/task.py +417 -0
- mindcode/cli/commands/terminal.py +124 -0
- mindcode/cli/shell/__init__.py +5 -0
- mindcode/cli/shell/__pycache__/__init__.cpython-313.pyc +0 -0
- mindcode/cli/shell/__pycache__/completion.cpython-313.pyc +0 -0
- mindcode/cli/shell/__pycache__/menu.cpython-313.pyc +0 -0
- mindcode/cli/shell/__pycache__/repl.cpython-313.pyc +0 -0
- mindcode/cli/shell/__pycache__/slash.cpython-313.pyc +0 -0
- mindcode/cli/shell/__pycache__/startup.cpython-313.pyc +0 -0
- mindcode/cli/shell/completion.py +72 -0
- mindcode/cli/shell/menu.py +88 -0
- mindcode/cli/shell/repl.py +597 -0
- mindcode/cli/shell/slash.py +480 -0
- mindcode/cli/shell/startup.py +55 -0
- mindcode/cli/shell/tui.py +897 -0
- mindcode/config.py +173 -0
- mindcode/mcp.py +205 -0
- mindcode/policy.py +173 -0
- mindcode/remote.py +216 -0
- mindcode/render.py +458 -0
- mindcode/runtime.py +541 -0
- mindcode/skills.py +154 -0
- mindcode/subagents.py +43 -0
- mindcode/tasking.py +281 -0
- mindcode/terminal/__init__.py +1 -0
- mindcode/terminal/__main__.py +17 -0
- mindcode/terminal/__pycache__/__init__.cpython-313.pyc +0 -0
- mindcode/terminal/__pycache__/__main__.cpython-313.pyc +0 -0
- mindcode/terminal_core.py +264 -0
- mindcode-0.2.0.dist-info/METADATA +244 -0
- mindcode-0.2.0.dist-info/RECORD +116 -0
- mindcode-0.2.0.dist-info/WHEEL +5 -0
- mindcode-0.2.0.dist-info/entry_points.txt +2 -0
- mindcode-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from mindagent.core import AgentResult
|
|
6
|
+
|
|
7
|
+
from ..schema import BenchmarkCase, CaseTrace, ScoreResult
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TerminalBenchRunScorer:
|
|
11
|
+
def score(
|
|
12
|
+
self,
|
|
13
|
+
case: BenchmarkCase,
|
|
14
|
+
run_result: AgentResult | None,
|
|
15
|
+
workspace: Path,
|
|
16
|
+
trace: CaseTrace,
|
|
17
|
+
) -> ScoreResult:
|
|
18
|
+
passed = run_result is not None and run_result.error is None
|
|
19
|
+
return ScoreResult(
|
|
20
|
+
passed=passed,
|
|
21
|
+
score=1.0 if passed else 0.0,
|
|
22
|
+
metrics={"workspace_diff_bytes": len(trace.workspace_diff.encode("utf-8"))},
|
|
23
|
+
failure_phase=None if passed else "tool_execution",
|
|
24
|
+
failure_reason=None if passed else "agent_run_failed",
|
|
25
|
+
)
|
mindcode/bench/trace.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from mindagent.core import AgentEvent, EventType
|
|
6
|
+
|
|
7
|
+
from .jsonutil import to_jsonable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BenchmarkTraceRecorder:
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
self.agent_events: list[dict[str, Any]] = []
|
|
13
|
+
self.tool_calls: list[dict[str, Any]] = []
|
|
14
|
+
|
|
15
|
+
async def __call__(self, event: AgentEvent) -> None:
|
|
16
|
+
serialized = {
|
|
17
|
+
"event_id": event.event_id,
|
|
18
|
+
"run_id": event.run_id,
|
|
19
|
+
"event_type": event.event_type.value,
|
|
20
|
+
"state": event.state.value,
|
|
21
|
+
"timestamp": event.timestamp,
|
|
22
|
+
"payload": to_jsonable(event.payload),
|
|
23
|
+
}
|
|
24
|
+
self.agent_events.append(serialized)
|
|
25
|
+
if event.event_type == EventType.ACTION_STARTED:
|
|
26
|
+
payload = serialized["payload"]
|
|
27
|
+
self.tool_calls.append(
|
|
28
|
+
{
|
|
29
|
+
"name": payload.get("action_name") or payload.get("name"),
|
|
30
|
+
"arguments": payload.get("arguments", {}),
|
|
31
|
+
"event_id": event.event_id,
|
|
32
|
+
"timestamp": event.timestamp,
|
|
33
|
+
}
|
|
34
|
+
)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import difflib
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from mindagent.core.artifact import (
|
|
7
|
+
ensure_artifact_directory,
|
|
8
|
+
validate_artifact_path,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from .schema import BenchmarkCase
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def prepare_case_workspace(root: Path, case: BenchmarkCase) -> Path:
|
|
15
|
+
root = root.expanduser()
|
|
16
|
+
ensure_artifact_directory(root)
|
|
17
|
+
root = root.resolve()
|
|
18
|
+
workspace = (root / case.case_id).resolve()
|
|
19
|
+
try:
|
|
20
|
+
workspace.relative_to(root)
|
|
21
|
+
except ValueError as exc:
|
|
22
|
+
raise ValueError("benchmark case_id 不能超出 workspace root") from exc
|
|
23
|
+
workspace.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
for item in case.files:
|
|
25
|
+
candidate = workspace / item.path
|
|
26
|
+
try:
|
|
27
|
+
validate_artifact_path(workspace, candidate)
|
|
28
|
+
except ValueError as exc:
|
|
29
|
+
raise ValueError(
|
|
30
|
+
f"benchmark file 不能超出 case workspace: {item.path}"
|
|
31
|
+
) from exc
|
|
32
|
+
target = candidate.resolve()
|
|
33
|
+
try:
|
|
34
|
+
target.relative_to(workspace)
|
|
35
|
+
except ValueError as exc:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
f"benchmark file 不能超出 case workspace: {item.path}"
|
|
38
|
+
) from exc
|
|
39
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
40
|
+
target.write_text(item.content, encoding="utf-8")
|
|
41
|
+
return workspace
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def workspace_diff(case: BenchmarkCase, workspace: Path) -> str:
|
|
45
|
+
workspace = Path(workspace)
|
|
46
|
+
try:
|
|
47
|
+
# A symlinked workspace is outside the benchmark observation boundary
|
|
48
|
+
# even when its resolved target happens to exist.
|
|
49
|
+
if workspace.is_symlink():
|
|
50
|
+
return ""
|
|
51
|
+
resolved_workspace = workspace.resolve(strict=False)
|
|
52
|
+
if not resolved_workspace.is_dir():
|
|
53
|
+
return ""
|
|
54
|
+
except (OSError, RuntimeError):
|
|
55
|
+
return ""
|
|
56
|
+
|
|
57
|
+
parts: list[str] = []
|
|
58
|
+
initial = {item.path: item.content for item in case.files}
|
|
59
|
+
seen = set(initial)
|
|
60
|
+
|
|
61
|
+
for rel_path, before in sorted(initial.items()):
|
|
62
|
+
path = workspace / rel_path
|
|
63
|
+
after = _read_workspace_file(path, resolved_workspace)
|
|
64
|
+
if before != after:
|
|
65
|
+
parts.extend(
|
|
66
|
+
difflib.unified_diff(
|
|
67
|
+
before.splitlines(keepends=True),
|
|
68
|
+
after.splitlines(keepends=True),
|
|
69
|
+
fromfile=f"a/{rel_path}",
|
|
70
|
+
tofile=f"b/{rel_path}",
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
for path in sorted(workspace.rglob("*")):
|
|
75
|
+
if _workspace_entry(path, resolved_workspace) is None:
|
|
76
|
+
continue
|
|
77
|
+
if not path.is_file():
|
|
78
|
+
continue
|
|
79
|
+
rel_path = path.relative_to(workspace).as_posix()
|
|
80
|
+
if rel_path in seen:
|
|
81
|
+
continue
|
|
82
|
+
after = _read_workspace_file(path, resolved_workspace, errors="replace")
|
|
83
|
+
parts.extend(
|
|
84
|
+
difflib.unified_diff(
|
|
85
|
+
[],
|
|
86
|
+
after.splitlines(keepends=True),
|
|
87
|
+
fromfile=f"a/{rel_path}",
|
|
88
|
+
tofile=f"b/{rel_path}",
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return "".join(parts)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _read_workspace_file(
|
|
96
|
+
path: Path,
|
|
97
|
+
workspace: Path,
|
|
98
|
+
*,
|
|
99
|
+
errors: str | None = None,
|
|
100
|
+
) -> str:
|
|
101
|
+
"""Read only ordinary files contained by the resolved workspace."""
|
|
102
|
+
if _workspace_entry(path, workspace) is None or not path.is_file():
|
|
103
|
+
return ""
|
|
104
|
+
try:
|
|
105
|
+
if errors is None:
|
|
106
|
+
return path.read_text(encoding="utf-8")
|
|
107
|
+
return path.read_text(encoding="utf-8", errors=errors)
|
|
108
|
+
except (OSError, UnicodeError):
|
|
109
|
+
return ""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _workspace_entry(path: Path, workspace: Path) -> Path | None:
|
|
113
|
+
"""Return a path only when no component follows a symlink or escapes."""
|
|
114
|
+
try:
|
|
115
|
+
if path.is_symlink():
|
|
116
|
+
return None
|
|
117
|
+
resolved = path.resolve(strict=False)
|
|
118
|
+
resolved.relative_to(workspace)
|
|
119
|
+
relative = path.relative_to(workspace)
|
|
120
|
+
current = workspace
|
|
121
|
+
for component in relative.parts:
|
|
122
|
+
current /= component
|
|
123
|
+
if current.is_symlink():
|
|
124
|
+
return None
|
|
125
|
+
return path
|
|
126
|
+
except (OSError, RuntimeError, ValueError):
|
|
127
|
+
return None
|
mindcode/cli/__init__.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""mindcode CLI 入口:typer app + 命令注册。
|
|
2
|
+
|
|
3
|
+
模块结构(对齐 mindbot/src/mindbot/cli/):
|
|
4
|
+
cli/__init__.py typer app + 命令注册
|
|
5
|
+
cli/_shared.py 共享 console + find_config_file
|
|
6
|
+
cli/commands/
|
|
7
|
+
shell.py `mindcode shell`(默认子命令)
|
|
8
|
+
chat.py `mindcode chat -m "..."`
|
|
9
|
+
task.py `mindcode task run/list/status/tail`
|
|
10
|
+
status.py `mindcode status`
|
|
11
|
+
config_cmd.py `mindcode config providers/models/api-key/...`
|
|
12
|
+
cli/shell/
|
|
13
|
+
__init__.py 暴露 Shell / run_shell
|
|
14
|
+
repl.py Shell 类 + REPL 主循环
|
|
15
|
+
slash.py slash 命令分发
|
|
16
|
+
menu.py interactive_menu(ChoiceInput 包装)
|
|
17
|
+
startup.py build_welcome_banner
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import typer
|
|
23
|
+
|
|
24
|
+
from .._version import __version__
|
|
25
|
+
from ._shared import console
|
|
26
|
+
|
|
27
|
+
KNOWN_SUBCOMMANDS = {
|
|
28
|
+
"shell",
|
|
29
|
+
"chat",
|
|
30
|
+
"status",
|
|
31
|
+
"config",
|
|
32
|
+
"bench",
|
|
33
|
+
"task",
|
|
34
|
+
"remote",
|
|
35
|
+
"terminal",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DefaultShellGroup(typer.core.TyperGroup):
|
|
40
|
+
"""让 `mindcode`(不带子命令)默认进 shell。
|
|
41
|
+
|
|
42
|
+
当第一个非 flag 参数不是已知子命令时,把 "shell" 注入到 args 前面,
|
|
43
|
+
让 click 走完整的参数解析路径。
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def parse_args(self, ctx, args):
|
|
47
|
+
if args:
|
|
48
|
+
first = args[0]
|
|
49
|
+
if (
|
|
50
|
+
first in KNOWN_SUBCOMMANDS
|
|
51
|
+
or first in {"--help", "-h", "--version", "-v"}
|
|
52
|
+
):
|
|
53
|
+
return super().parse_args(ctx, args)
|
|
54
|
+
# 无子命令或第一参数是 shell 的 flag/位置参数 → 注入 shell
|
|
55
|
+
new_args = ["shell", *args]
|
|
56
|
+
return super().parse_args(ctx, new_args)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
app = typer.Typer(
|
|
60
|
+
name="mindcode",
|
|
61
|
+
help=(
|
|
62
|
+
f"mindcode v{__version__} — task execution and development "
|
|
63
|
+
"supervision agent"
|
|
64
|
+
),
|
|
65
|
+
cls=DefaultShellGroup,
|
|
66
|
+
no_args_is_help=False,
|
|
67
|
+
add_completion=False,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _version_callback(value: bool) -> None:
|
|
72
|
+
if value:
|
|
73
|
+
console.print(f"mindcode v{__version__}")
|
|
74
|
+
raise typer.Exit()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@app.callback()
|
|
78
|
+
def main(
|
|
79
|
+
version: bool = typer.Option(
|
|
80
|
+
None,
|
|
81
|
+
"--version",
|
|
82
|
+
"-v",
|
|
83
|
+
callback=_version_callback,
|
|
84
|
+
is_eager=True,
|
|
85
|
+
help="显示版本号",
|
|
86
|
+
),
|
|
87
|
+
) -> None:
|
|
88
|
+
"""mindcode — task execution and development supervision agent."""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# 子命令注册
|
|
92
|
+
from .commands.chat import chat # noqa: E402
|
|
93
|
+
from .commands.bench import app as bench_app # noqa: E402
|
|
94
|
+
from .commands.config_cmd import app as config_app # noqa: E402
|
|
95
|
+
from .commands.remote import app as remote_app # noqa: E402
|
|
96
|
+
from .commands.shell import shell # noqa: E402
|
|
97
|
+
from .commands.status import status # noqa: E402
|
|
98
|
+
from .commands.task import app as task_app # noqa: E402
|
|
99
|
+
from .commands.terminal import app as terminal_app # noqa: E402
|
|
100
|
+
|
|
101
|
+
app.command("shell")(shell)
|
|
102
|
+
app.command()(chat)
|
|
103
|
+
app.command()(status)
|
|
104
|
+
app.add_typer(config_app, name="config")
|
|
105
|
+
app.add_typer(bench_app, name="bench")
|
|
106
|
+
app.add_typer(task_app, name="task")
|
|
107
|
+
app.add_typer(remote_app, name="remote")
|
|
108
|
+
app.add_typer(terminal_app, name="terminal")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
app()
|
|
Binary file
|
|
Binary file
|
mindcode/cli/_shared.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""CLI 共享工具:全局 console 实例与配置文件定位。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from ..config import CONFIG_FILE
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _StdoutProxy:
|
|
14
|
+
"""每次 write 时动态查 sys.stdout,使 patch_stdout 能拦截 Rich 输出。"""
|
|
15
|
+
|
|
16
|
+
def write(self, data: str) -> int:
|
|
17
|
+
return sys.stdout.write(data)
|
|
18
|
+
|
|
19
|
+
def flush(self) -> None:
|
|
20
|
+
sys.stdout.flush()
|
|
21
|
+
|
|
22
|
+
def isatty(self) -> bool:
|
|
23
|
+
return sys.stdout.isatty()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
console = Console(file=_StdoutProxy())
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def mask_key(key: str) -> str:
|
|
30
|
+
if not key:
|
|
31
|
+
return "[red](未设置)[/red]"
|
|
32
|
+
if len(key) <= 8:
|
|
33
|
+
return "*" * len(key)
|
|
34
|
+
return key[:4] + "..." + key[-4:]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def find_config_file() -> Path:
|
|
38
|
+
"""返回配置文件路径(首次访问时自动 bootstrap)。"""
|
|
39
|
+
if not CONFIG_FILE.exists():
|
|
40
|
+
from ..config import load_config
|
|
41
|
+
|
|
42
|
+
load_config()
|
|
43
|
+
return CONFIG_FILE
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""commands 子包入口。"""
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|