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.
Files changed (116) hide show
  1. mindcode/__init__.py +5 -0
  2. mindcode/__main__.py +4 -0
  3. mindcode/__pycache__/__init__.cpython-313.pyc +0 -0
  4. mindcode/__pycache__/__main__.cpython-313.pyc +0 -0
  5. mindcode/__pycache__/_version.cpython-313.pyc +0 -0
  6. mindcode/__pycache__/config.cpython-313.pyc +0 -0
  7. mindcode/__pycache__/policy.cpython-313.pyc +0 -0
  8. mindcode/__pycache__/remote.cpython-313.pyc +0 -0
  9. mindcode/__pycache__/render.cpython-313.pyc +0 -0
  10. mindcode/__pycache__/runtime.cpython-313.pyc +0 -0
  11. mindcode/__pycache__/tasking.cpython-313.pyc +0 -0
  12. mindcode/__pycache__/terminal_core.cpython-313.pyc +0 -0
  13. mindcode/_version.py +11 -0
  14. mindcode/approval.py +105 -0
  15. mindcode/bench/__init__.py +19 -0
  16. mindcode/bench/__pycache__/__init__.cpython-313.pyc +0 -0
  17. mindcode/bench/__pycache__/fake_tools.cpython-313.pyc +0 -0
  18. mindcode/bench/__pycache__/jsonutil.cpython-313.pyc +0 -0
  19. mindcode/bench/__pycache__/paths.cpython-313.pyc +0 -0
  20. mindcode/bench/__pycache__/predictions.cpython-313.pyc +0 -0
  21. mindcode/bench/__pycache__/report.cpython-313.pyc +0 -0
  22. mindcode/bench/__pycache__/runner.cpython-313.pyc +0 -0
  23. mindcode/bench/__pycache__/schema.cpython-313.pyc +0 -0
  24. mindcode/bench/__pycache__/trace.cpython-313.pyc +0 -0
  25. mindcode/bench/__pycache__/workspace.cpython-313.pyc +0 -0
  26. mindcode/bench/adapters/__init__.py +22 -0
  27. mindcode/bench/adapters/__pycache__/__init__.cpython-313.pyc +0 -0
  28. mindcode/bench/adapters/__pycache__/base.cpython-313.pyc +0 -0
  29. mindcode/bench/adapters/__pycache__/mini_bfcl.cpython-313.pyc +0 -0
  30. mindcode/bench/adapters/__pycache__/mini_gaia.cpython-313.pyc +0 -0
  31. mindcode/bench/adapters/__pycache__/mini_terminal.cpython-313.pyc +0 -0
  32. mindcode/bench/adapters/__pycache__/swe_bench.cpython-313.pyc +0 -0
  33. mindcode/bench/adapters/__pycache__/terminal_bench.cpython-313.pyc +0 -0
  34. mindcode/bench/adapters/base.py +45 -0
  35. mindcode/bench/adapters/mini_bfcl.py +18 -0
  36. mindcode/bench/adapters/mini_gaia.py +18 -0
  37. mindcode/bench/adapters/mini_terminal.py +18 -0
  38. mindcode/bench/adapters/swe_bench.py +86 -0
  39. mindcode/bench/adapters/terminal_bench.py +107 -0
  40. mindcode/bench/fake_tools.py +73 -0
  41. mindcode/bench/jsonutil.py +20 -0
  42. mindcode/bench/paths.py +10 -0
  43. mindcode/bench/predictions.py +65 -0
  44. mindcode/bench/report.py +48 -0
  45. mindcode/bench/runner.py +250 -0
  46. mindcode/bench/schema.py +78 -0
  47. mindcode/bench/scorers/__pycache__/base.cpython-313.pyc +0 -0
  48. mindcode/bench/scorers/__pycache__/composite.cpython-313.pyc +0 -0
  49. mindcode/bench/scorers/__pycache__/exact.cpython-313.pyc +0 -0
  50. mindcode/bench/scorers/__pycache__/json_call.cpython-313.pyc +0 -0
  51. mindcode/bench/scorers/__pycache__/swe.cpython-313.pyc +0 -0
  52. mindcode/bench/scorers/__pycache__/terminal.cpython-313.pyc +0 -0
  53. mindcode/bench/scorers/__pycache__/terminal_bench.cpython-313.pyc +0 -0
  54. mindcode/bench/scorers/base.py +22 -0
  55. mindcode/bench/scorers/composite.py +57 -0
  56. mindcode/bench/scorers/exact.py +30 -0
  57. mindcode/bench/scorers/json_call.py +44 -0
  58. mindcode/bench/scorers/swe.py +29 -0
  59. mindcode/bench/scorers/terminal.py +43 -0
  60. mindcode/bench/scorers/terminal_bench.py +25 -0
  61. mindcode/bench/trace.py +34 -0
  62. mindcode/bench/workspace.py +127 -0
  63. mindcode/cli/__init__.py +112 -0
  64. mindcode/cli/__pycache__/__init__.cpython-313.pyc +0 -0
  65. mindcode/cli/__pycache__/_shared.cpython-313.pyc +0 -0
  66. mindcode/cli/_shared.py +43 -0
  67. mindcode/cli/commands/__init__.py +1 -0
  68. mindcode/cli/commands/__pycache__/__init__.cpython-313.pyc +0 -0
  69. mindcode/cli/commands/__pycache__/bench.cpython-313.pyc +0 -0
  70. mindcode/cli/commands/__pycache__/chat.cpython-313.pyc +0 -0
  71. mindcode/cli/commands/__pycache__/config_cmd.cpython-313.pyc +0 -0
  72. mindcode/cli/commands/__pycache__/remote.cpython-313.pyc +0 -0
  73. mindcode/cli/commands/__pycache__/shell.cpython-313.pyc +0 -0
  74. mindcode/cli/commands/__pycache__/status.cpython-313.pyc +0 -0
  75. mindcode/cli/commands/__pycache__/task.cpython-313.pyc +0 -0
  76. mindcode/cli/commands/__pycache__/terminal.cpython-313.pyc +0 -0
  77. mindcode/cli/commands/bench.py +365 -0
  78. mindcode/cli/commands/chat.py +86 -0
  79. mindcode/cli/commands/config_cmd.py +197 -0
  80. mindcode/cli/commands/remote.py +146 -0
  81. mindcode/cli/commands/shell.py +108 -0
  82. mindcode/cli/commands/status.py +37 -0
  83. mindcode/cli/commands/task.py +417 -0
  84. mindcode/cli/commands/terminal.py +124 -0
  85. mindcode/cli/shell/__init__.py +5 -0
  86. mindcode/cli/shell/__pycache__/__init__.cpython-313.pyc +0 -0
  87. mindcode/cli/shell/__pycache__/completion.cpython-313.pyc +0 -0
  88. mindcode/cli/shell/__pycache__/menu.cpython-313.pyc +0 -0
  89. mindcode/cli/shell/__pycache__/repl.cpython-313.pyc +0 -0
  90. mindcode/cli/shell/__pycache__/slash.cpython-313.pyc +0 -0
  91. mindcode/cli/shell/__pycache__/startup.cpython-313.pyc +0 -0
  92. mindcode/cli/shell/completion.py +72 -0
  93. mindcode/cli/shell/menu.py +88 -0
  94. mindcode/cli/shell/repl.py +597 -0
  95. mindcode/cli/shell/slash.py +480 -0
  96. mindcode/cli/shell/startup.py +55 -0
  97. mindcode/cli/shell/tui.py +897 -0
  98. mindcode/config.py +173 -0
  99. mindcode/mcp.py +205 -0
  100. mindcode/policy.py +173 -0
  101. mindcode/remote.py +216 -0
  102. mindcode/render.py +458 -0
  103. mindcode/runtime.py +541 -0
  104. mindcode/skills.py +154 -0
  105. mindcode/subagents.py +43 -0
  106. mindcode/tasking.py +281 -0
  107. mindcode/terminal/__init__.py +1 -0
  108. mindcode/terminal/__main__.py +17 -0
  109. mindcode/terminal/__pycache__/__init__.cpython-313.pyc +0 -0
  110. mindcode/terminal/__pycache__/__main__.cpython-313.pyc +0 -0
  111. mindcode/terminal_core.py +264 -0
  112. mindcode-0.2.0.dist-info/METADATA +244 -0
  113. mindcode-0.2.0.dist-info/RECORD +116 -0
  114. mindcode-0.2.0.dist-info/WHEEL +5 -0
  115. mindcode-0.2.0.dist-info/entry_points.txt +2 -0
  116. 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}")
@@ -0,0 +1,5 @@
1
+ """Shell 子包入口。"""
2
+
3
+ from .repl import run_shell
4
+
5
+ __all__ = ["run_shell"]
@@ -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"}