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,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from ...terminal_core import (
|
|
11
|
+
TerminalManager,
|
|
12
|
+
TerminalStore,
|
|
13
|
+
build_ghostty_open_argv,
|
|
14
|
+
tail_text,
|
|
15
|
+
)
|
|
16
|
+
from .._shared import console
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(help="管理本地交互式 terminal session")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.command("open")
|
|
23
|
+
def terminal_open(
|
|
24
|
+
cwd: str = typer.Option(".", "--cwd", "-C", help="terminal 工作目录"),
|
|
25
|
+
command: str | None = typer.Option(None, "--command", "-e", help="启动后执行的命令"),
|
|
26
|
+
app_path: str = typer.Option(
|
|
27
|
+
"/Applications/Ghostty.app",
|
|
28
|
+
"--app-path",
|
|
29
|
+
help="Ghostty.app 路径",
|
|
30
|
+
),
|
|
31
|
+
) -> None:
|
|
32
|
+
path = Path(cwd).expanduser().resolve()
|
|
33
|
+
if not path.is_dir():
|
|
34
|
+
console.print(f"[red]cwd 不存在或不是目录:[/red] {path}")
|
|
35
|
+
raise typer.Exit(2)
|
|
36
|
+
argv = build_ghostty_open_argv(cwd=path, command=command, app_path=app_path)
|
|
37
|
+
try:
|
|
38
|
+
subprocess.Popen(argv)
|
|
39
|
+
except FileNotFoundError:
|
|
40
|
+
console.print("[red]找不到 macOS open 命令[/red]")
|
|
41
|
+
raise typer.Exit(2)
|
|
42
|
+
console.print("opened Ghostty")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command("new")
|
|
46
|
+
def terminal_new(
|
|
47
|
+
cwd: str = typer.Option(".", "--cwd", "-C", help="terminal 工作目录"),
|
|
48
|
+
shell: str | None = typer.Option(None, "--shell", help="shell 路径"),
|
|
49
|
+
) -> None:
|
|
50
|
+
try:
|
|
51
|
+
record = TerminalManager().start(cwd=Path(cwd), shell=shell)
|
|
52
|
+
except ValueError as exc:
|
|
53
|
+
console.print(f"[red]terminal 创建失败:[/red] {exc}")
|
|
54
|
+
raise typer.Exit(2)
|
|
55
|
+
console.print(f"terminal_id={record.terminal_id}")
|
|
56
|
+
console.print(f"pid={record.pid}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.command("list")
|
|
60
|
+
def terminal_list() -> None:
|
|
61
|
+
store = TerminalStore()
|
|
62
|
+
table = Table("terminal_id", "status", "pid", "cwd", "shell")
|
|
63
|
+
for record in store.list():
|
|
64
|
+
table.add_row(
|
|
65
|
+
record.terminal_id,
|
|
66
|
+
record.status,
|
|
67
|
+
str(record.pid or ""),
|
|
68
|
+
record.cwd,
|
|
69
|
+
record.shell,
|
|
70
|
+
)
|
|
71
|
+
console.print(table)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.command("send")
|
|
75
|
+
def terminal_send(
|
|
76
|
+
terminal_id: str,
|
|
77
|
+
text: str,
|
|
78
|
+
no_newline: bool = typer.Option(
|
|
79
|
+
False,
|
|
80
|
+
"--no-newline",
|
|
81
|
+
help="不追加换行",
|
|
82
|
+
),
|
|
83
|
+
) -> None:
|
|
84
|
+
try:
|
|
85
|
+
TerminalManager().send(terminal_id, text, newline=not no_newline)
|
|
86
|
+
except (FileNotFoundError, RuntimeError) as exc:
|
|
87
|
+
console.print(f"[red]send 失败:[/red] {exc}")
|
|
88
|
+
raise typer.Exit(2)
|
|
89
|
+
console.print("sent")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@app.command("tail")
|
|
93
|
+
def terminal_tail(
|
|
94
|
+
terminal_id: str,
|
|
95
|
+
lines: int = typer.Option(40, "--lines", "-n", help="显示最近行数"),
|
|
96
|
+
follow: bool = typer.Option(False, "--follow", "-f", help="持续追踪"),
|
|
97
|
+
) -> None:
|
|
98
|
+
store = TerminalStore()
|
|
99
|
+
path = store.log_path(terminal_id)
|
|
100
|
+
if not path.exists():
|
|
101
|
+
console.print(f"[red]terminal log 不存在:[/red] {terminal_id}")
|
|
102
|
+
raise typer.Exit(2)
|
|
103
|
+
console.print(tail_text(path, lines))
|
|
104
|
+
if not follow:
|
|
105
|
+
return
|
|
106
|
+
offset = path.stat().st_size
|
|
107
|
+
while True:
|
|
108
|
+
time.sleep(1)
|
|
109
|
+
with path.open("r", encoding="utf-8", errors="replace") as file:
|
|
110
|
+
file.seek(offset)
|
|
111
|
+
chunk = file.read()
|
|
112
|
+
if chunk:
|
|
113
|
+
console.print(chunk, end="")
|
|
114
|
+
offset = file.tell()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@app.command("close")
|
|
118
|
+
def terminal_close(terminal_id: str) -> None:
|
|
119
|
+
try:
|
|
120
|
+
record = TerminalManager().close(terminal_id)
|
|
121
|
+
except FileNotFoundError:
|
|
122
|
+
console.print(f"[red]terminal 不存在:[/red] {terminal_id}")
|
|
123
|
+
raise typer.Exit(2)
|
|
124
|
+
console.print(f"closed {record.terminal_id}")
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
6
|
+
from prompt_toolkit.document import Document
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
SLASH_COMMANDS = {
|
|
10
|
+
"models": "切换当前模型",
|
|
11
|
+
"compact": "压缩整理当前会话上下文",
|
|
12
|
+
"permissions": "切换权限模式",
|
|
13
|
+
"status": "显示当前会话状态",
|
|
14
|
+
"config": "显示配置",
|
|
15
|
+
"sessions": "列出本地会话",
|
|
16
|
+
"history": "打印当前会话消息",
|
|
17
|
+
"diff": "显示最后一次 diff",
|
|
18
|
+
"clear": "清空当前会话历史",
|
|
19
|
+
"help": "显示帮助",
|
|
20
|
+
"exit": "退出",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MindcodeCompleter(Completer):
|
|
25
|
+
def __init__(self, state: Any):
|
|
26
|
+
self.state = state
|
|
27
|
+
|
|
28
|
+
def get_completions(self, document: Document, complete_event):
|
|
29
|
+
text = document.text_before_cursor
|
|
30
|
+
if not text.startswith("/"):
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
body = text[1:]
|
|
34
|
+
parts = body.split()
|
|
35
|
+
command_prefix = body if " " not in body else parts[0]
|
|
36
|
+
|
|
37
|
+
if " " not in body:
|
|
38
|
+
yield from _complete_words(
|
|
39
|
+
[f"/{name}" for name in SLASH_COMMANDS],
|
|
40
|
+
"/" + command_prefix,
|
|
41
|
+
descriptions=SLASH_COMMANDS,
|
|
42
|
+
)
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
command = parts[0] if parts else ""
|
|
46
|
+
arg_prefix = body[len(command):].lstrip()
|
|
47
|
+
if command == "models":
|
|
48
|
+
provider = self.state.config.providers.get(self.state.active_provider)
|
|
49
|
+
if provider:
|
|
50
|
+
yield from _complete_words(provider.models, arg_prefix)
|
|
51
|
+
return
|
|
52
|
+
if command == "permissions":
|
|
53
|
+
yield from _complete_words(["ask_user", "full_accept"], arg_prefix)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _complete_words(
|
|
57
|
+
options: list[str],
|
|
58
|
+
prefix: str,
|
|
59
|
+
*,
|
|
60
|
+
descriptions: dict[str, str] | None = None,
|
|
61
|
+
):
|
|
62
|
+
for option in options:
|
|
63
|
+
if not option.startswith(prefix):
|
|
64
|
+
continue
|
|
65
|
+
display_meta = ""
|
|
66
|
+
if descriptions:
|
|
67
|
+
display_meta = descriptions.get(option.lstrip("/"), "")
|
|
68
|
+
yield Completion(
|
|
69
|
+
option,
|
|
70
|
+
start_position=-len(prefix),
|
|
71
|
+
display_meta=display_meta,
|
|
72
|
+
)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""交互式方向键选择菜单(prompt_toolkit ChoiceInput 包装)。"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def interactive_menu(
|
|
7
|
+
title: str,
|
|
8
|
+
options: list[tuple[str, str]],
|
|
9
|
+
default: str | None = None,
|
|
10
|
+
) -> str | None:
|
|
11
|
+
"""Show a navigable menu and return the selected value.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
title: prompt message
|
|
15
|
+
options: list of (value, label); label supports Rich markup but
|
|
16
|
+
ChoiceInput only renders plain text — keep labels short
|
|
17
|
+
default: optional default value
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
selected value, or None if user cancelled (Ctrl-C / Ctrl-D / empty)
|
|
21
|
+
"""
|
|
22
|
+
try:
|
|
23
|
+
from prompt_toolkit.shortcuts.choice_input import ChoiceInput
|
|
24
|
+
except ImportError:
|
|
25
|
+
return await _fallback_menu(title, options, default)
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
result = await ChoiceInput(
|
|
29
|
+
message=title,
|
|
30
|
+
options=options,
|
|
31
|
+
default=default if default in [v for v, _ in options] else None,
|
|
32
|
+
).prompt_async()
|
|
33
|
+
return result
|
|
34
|
+
except (EOFError, KeyboardInterrupt):
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _fallback_menu(
|
|
39
|
+
title: str,
|
|
40
|
+
options: list[tuple[str, str]],
|
|
41
|
+
default: str | None,
|
|
42
|
+
) -> str | None:
|
|
43
|
+
"""序号输入 fallback(无 prompt_toolkit 时用)。"""
|
|
44
|
+
import asyncio
|
|
45
|
+
|
|
46
|
+
print(f"\n{title}")
|
|
47
|
+
for idx, (_value, label) in enumerate(options, 1):
|
|
48
|
+
mark = " *" if default and options[idx - 1][0] == default else ""
|
|
49
|
+
print(f" [{idx}] {label}{mark}")
|
|
50
|
+
answer = await asyncio.to_thread(
|
|
51
|
+
input, f"选择 [1-{len(options)}],回车退出: "
|
|
52
|
+
)
|
|
53
|
+
answer = answer.strip()
|
|
54
|
+
if not answer:
|
|
55
|
+
return None
|
|
56
|
+
try:
|
|
57
|
+
idx = int(answer) - 1
|
|
58
|
+
except ValueError:
|
|
59
|
+
# 按名字匹配
|
|
60
|
+
for value, _label in options:
|
|
61
|
+
if value == answer:
|
|
62
|
+
return value
|
|
63
|
+
return None
|
|
64
|
+
if 0 <= idx < len(options):
|
|
65
|
+
return options[idx][0]
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async def prompt_string(message: str, *, default: str = "") -> str:
|
|
70
|
+
"""简单字符串输入;使用 prompt_toolkit 保留方向键编辑能力。"""
|
|
71
|
+
try:
|
|
72
|
+
from prompt_toolkit import PromptSession
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
await PromptSession(message=message).prompt_async(default=default)
|
|
76
|
+
).strip()
|
|
77
|
+
except (EOFError, KeyboardInterrupt):
|
|
78
|
+
return ""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def prompt_yes_no(message: str, *, default_yes: bool = True) -> bool:
|
|
82
|
+
"""Y/N 确认,回车走 default。"""
|
|
83
|
+
hint = "[Y/n]" if default_yes else "[y/N]"
|
|
84
|
+
answer = await prompt_string(f"{message} {hint} ")
|
|
85
|
+
answer = answer.strip().lower()
|
|
86
|
+
if not answer:
|
|
87
|
+
return default_yes
|
|
88
|
+
return answer in {"y", "yes"}
|