mini-code-cli 0.1.1.2__py3-none-any.whl → 0.1.1.4__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.
mini_code_cli/__init__.py CHANGED
@@ -11,22 +11,41 @@ to ``"unknown"`` so importing the package can never fail over a version lookup.
11
11
  __all__ = ["__version__"]
12
12
 
13
13
 
14
+ def _version_from_file(path):
15
+ """Return the ``[project] version`` in ``path``.
16
+
17
+ ``tomllib`` only exists from Python 3.11, and the scan below is not a
18
+ nicety for 3.10: without it this function used to skip the file entirely on
19
+ that interpreter and report the *installed* metadata instead, so a checkout
20
+ with a stale editable install showed the wrong version in the banner (found
21
+ on the 3.10 CI leg). Both paths read the same key, and the section tracking
22
+ matters: a ``version`` key in some other table must not win.
23
+ """
24
+ try:
25
+ import tomllib # Python >= 3.11
26
+ except ImportError: # pragma: no cover - exercised on the 3.10 CI leg
27
+ tomllib = None
28
+ if tomllib is not None:
29
+ with open(path, "rb") as f:
30
+ return str(tomllib.load(f)["project"]["version"])
31
+ section = None
32
+ with open(path, encoding="utf-8") as f:
33
+ for line in f:
34
+ stripped = line.strip()
35
+ if stripped.startswith("["):
36
+ section = stripped
37
+ elif section == "[project]" and stripped.startswith("version"):
38
+ return stripped.split("=", 1)[1].strip().strip('"')
39
+ raise KeyError(f"no version in the [project] table of {path}")
40
+
41
+
14
42
  def _read_version():
15
43
  """Best-effort package version: pyproject.toml, then installed metadata."""
16
44
  try:
17
45
  from pathlib import Path
18
46
 
19
47
  pyproject = Path(__file__).resolve().parent.parent / "pyproject.toml"
20
- try:
21
- import tomllib # Python >= 3.11
22
- except ImportError: # pragma: no cover - older interpreters
23
- tomllib = None
24
- if tomllib is not None:
25
- try:
26
- with open(pyproject, "rb") as f:
27
- return str(tomllib.load(f)["project"]["version"])
28
- except Exception:
29
- pass
48
+ return _version_from_file(pyproject)
30
49
  except Exception:
31
50
  pass
32
51
  try:
@@ -0,0 +1,12 @@
1
+ """Allow ``python -m mini_code_cli`` to start the agent.
2
+
3
+ The console script ``mini-code`` (see ``[project.scripts]``) is the normal entry
4
+ point; this module exists so the package also runs straight from a checkout or
5
+ an installed wheel without the script on ``PATH`` -- which is what a reader
6
+ tries first after ``pip install -e .``.
7
+ """
8
+
9
+ from .agent_loop import main
10
+
11
+ if __name__ == "__main__":
12
+ raise SystemExit(main())
@@ -0,0 +1,90 @@
1
+ """Agent-owned files kept in ``--allowed-dir/.mini``.
2
+
3
+ Two opt-in features put a file the *agent* may read and write there, rather
4
+ than in the session directory the loop owns:
5
+
6
+ * the **scratchpad** (``--scratchpad``): a free-form note file for thoughts,
7
+ plans and learnings, plus as many additional scratch files as the agent
8
+ wants to create;
9
+ * the **open plan** (``--plan-strategy open-plan``): a plan document the
10
+ agent maintains itself with the normal file tools.
11
+
12
+ Both live under the same ``.mini`` directory as the skills folder and the
13
+ latest-session pointer, so they are inside the ``--allowed-dir`` sandbox and
14
+ the normal ``read_file`` / ``write_file`` / ``edit`` tools can reach them. The
15
+ directory name is read from :mod:`mini_code_cli.persistence` at call time, so
16
+ the test suite can redirect it globally (exactly like the session pointer).
17
+ """
18
+
19
+ import os
20
+
21
+ from . import persistence
22
+
23
+ SCRATCHPAD_FILENAME = "SCRATCHPAD.md"
24
+ OPEN_PLAN_FILENAME = "PLAN.md"
25
+
26
+ _SCRATCHPAD_HEADER = """# Scratchpad
27
+
28
+ This file belongs to the agent. It may read and write it freely and may create
29
+ additional scratch files in the same directory. Use it for thoughts, plans,
30
+ learnings and notes that should survive a compaction.
31
+ """
32
+
33
+ _OPEN_PLAN_HEADER = """# Agent Plan
34
+
35
+ This plan document belongs to the agent. Read it before continuing, keep it up
36
+ to date as work progresses, and mark steps done or outstanding.
37
+ """
38
+
39
+
40
+ def mini_dir(allowed_dir):
41
+ """The ``.mini`` directory inside ``allowed_dir`` (not created)."""
42
+ return os.path.join(os.path.abspath(allowed_dir), persistence.SESSION_DIRNAME)
43
+
44
+
45
+ def scratchpad_path(allowed_dir):
46
+ """Path to the main scratch-pad file."""
47
+ return os.path.join(mini_dir(allowed_dir), SCRATCHPAD_FILENAME)
48
+
49
+
50
+ def open_plan_path(allowed_dir):
51
+ """Path to the agent-authored plan document."""
52
+ return os.path.join(mini_dir(allowed_dir), OPEN_PLAN_FILENAME)
53
+
54
+
55
+ def ensure_scratchpad(allowed_dir):
56
+ """Create the main scratch-pad file (and its directory) if missing."""
57
+ return _ensure_file(scratchpad_path(allowed_dir), _SCRATCHPAD_HEADER)
58
+
59
+
60
+ def ensure_open_plan(allowed_dir):
61
+ """Create the agent plan document (and its directory) if missing."""
62
+ return _ensure_file(open_plan_path(allowed_dir), _OPEN_PLAN_HEADER)
63
+
64
+
65
+ def read_text(path, limit=None):
66
+ """Read ``path`` as text, returning ``""`` when it cannot be read.
67
+
68
+ ``limit`` caps the returned characters (the open-plan re-injection after a
69
+ compaction should not be able to re-fill the context with the document).
70
+ """
71
+ try:
72
+ with open(path, "r", encoding="utf-8", errors="replace") as handle:
73
+ text = handle.read()
74
+ except OSError:
75
+ return ""
76
+ if limit is not None and len(text) > limit:
77
+ return text[:limit] + "\n[... truncated ...]\n"
78
+ return text
79
+
80
+
81
+ def _ensure_file(path, header):
82
+ """Best-effort create-with-header; never raises, always returns ``path``."""
83
+ try:
84
+ if not os.path.isfile(path):
85
+ os.makedirs(os.path.dirname(path), exist_ok=True)
86
+ with open(path, "w", encoding="utf-8") as handle:
87
+ handle.write(header)
88
+ except OSError:
89
+ pass
90
+ return path