mini-code-cli 0.1.0.1__py3-none-any.whl → 0.1.0.2__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.
@@ -52,6 +52,7 @@ from .persistence import (
52
52
  load_summary_from_dir,
53
53
  load_usage_from_dir,
54
54
  find_last_session_dir,
55
+ save_last_session_pointer,
55
56
  accumulate_usage,
56
57
  generate_unique_id,
57
58
  _restore_dir,
@@ -368,7 +369,15 @@ class AgentSession:
368
369
  restore_session = getattr(self.args, "restore_session", None)
369
370
  self.loaded_last_session_dir = None
370
371
  if getattr(self.args, "load_last_session", False) and not restore_session:
371
- detected = find_last_session_dir(self.cache_dir)
372
+ # Resolve the most recent session: the .mini pointer recorded under
373
+ # --allowed-dir is consulted first (an explicit record of the last
374
+ # session worked in), falling back to the timestamp heuristic over
375
+ # the cache dir.
376
+ detected = find_last_session_dir(
377
+ self.cache_dir,
378
+ allowed_dir=self.allowed_dir,
379
+ use_pointer=True,
380
+ )
372
381
  if detected:
373
382
  restore_session = detected
374
383
  self.loaded_last_session_dir = detected
@@ -585,6 +594,13 @@ class AgentSession:
585
594
  # user can see / copy the id this conversation is being saved under.
586
595
  self._print_session_id()
587
596
 
597
+ # Record this session as the "latest" so a future --load-last-session can
598
+ # resume it without scanning the cache dir. This lives in the top-level
599
+ # loop's own directory (cache_dir/unique_id), and only the interactive
600
+ # run() writes it -- a sub-agent child drives via _drive_auto and would
601
+ # otherwise overwrite the pointer with its own buried path.
602
+ self._save_last_session_pointer()
603
+
588
604
  # Report the outcome of --load-last-session up front, so the user sees
589
605
  # whether we resumed an earlier conversation (under a brand-new id) or
590
606
  # started fresh because no previous session was found.
@@ -616,6 +632,7 @@ class AgentSession:
616
632
  # 1) Get the user's turn: the one-shot preset, or a fresh line.
617
633
  if pending is not None:
618
634
  self.console.rule(title="Preset prompt", char="=", fg="cyan")
635
+ self.console.short_divider()
619
636
  self.console.turn("You", pending, accent="cyan")
620
637
  user_input = pending
621
638
  pending = None
@@ -654,18 +671,24 @@ class AgentSession:
654
671
  self._persist()
655
672
 
656
673
  def _print_session_id(self):
657
- """Print the session's unique id as an assistant-styled message.
674
+ """Print the session's unique id as a minimal "Assistant" turn.
658
675
 
659
- A short, styled announcement (in the assistant accent) so the user can
660
- see and copy the id the conversation is saved under -- the same id shown
661
- in the welcome banner, but rendered as a transcript turn so it is easy
662
- to spot and grep.
676
+ Instead of a banner/header the id is rendered as an assistant-styled
677
+ (green) turn carrying *only* the id in brackets, so the user can see /
678
+ copy the id this conversation is being saved under without the noise of
679
+ the extra "saved under" line.
663
680
  """
664
- self.console.divider()
665
- self.console.turn(
666
- "Session id",
667
- f"{self.unique_id} (saved under {self.cache_dir})",
668
- accent="green")
681
+ self.console.turn("Assistant", f"[{self.unique_id}]", accent="green")
682
+
683
+ def _save_last_session_pointer(self):
684
+ """Record this session's directory in --allowed-dir/.mini.
685
+
686
+ Writes the .mini pointer that a later --load-last-session consults first
687
+ (see save_last_session_pointer). Best-effort: never raises, so a pointer
688
+ hiccup can never break the interactive session.
689
+ """
690
+ save_last_session_pointer(self.allowed_dir,
691
+ os.path.join(self.cache_dir, self.unique_id))
669
692
 
670
693
  # -- autonomous drive (used by sub-agents) ------------------------------
671
694
 
@@ -833,6 +856,7 @@ class AgentSession:
833
856
 
834
857
  def _read_user_line(self):
835
858
  """Read one turn from the user. Returns the text to send, or None to quit."""
859
+ self.console.short_divider()
836
860
  user_input = self.console.read_line(style_line_prompt("You:"))
837
861
  if user_input is None: # EOF / Ctrl-C at the prompt
838
862
  self.console.note("Thank you. Goodbye! Saving history...", "info")
@@ -24,6 +24,7 @@ raising.
24
24
  import json
25
25
  import os
26
26
  import re
27
+ import time
27
28
 
28
29
  from . import ui
29
30
  from .config import DEFAULT_CACHE_LOCATION
@@ -76,8 +77,112 @@ def generate_unique_id():
76
77
  return f"{timestamp}-{uuid.uuid4().hex[:8]}"
77
78
 
78
79
 
79
- def find_last_session_dir(cache_dir):
80
- """Return the path to the most recently *active* session dir under ``cache_dir``.
80
+ # ---------------------------------------------------------------------------
81
+ # latest-session pointer (.mini)
82
+ # ---------------------------------------------------------------------------
83
+
84
+ # The .mini directory (inside the user's --allowed-dir) holds a tiny pointer to
85
+ # the most recent session. --load-last-session consults this pointer *first*
86
+ # (an explicit opt-in the user's environment recorded) before falling back to
87
+ # the timestamp heuristic in find_last_session_dir().
88
+ #
89
+ # NOTE: .mini is also the skills feature's directory. The pointer is a single
90
+ # dedicated file inside it (LAST_SESSION_FILENAME), so the two never collide and
91
+ # the skills code (which only reads <skill-name>/ + SKILLS.md) is unaffected.
92
+ SKILLS_DIRNAME = ".mini"
93
+ LAST_SESSION_FILENAME = "last_session.json"
94
+
95
+
96
+ def _last_session_dir_store(allowed_dir):
97
+ """Return the .mini directory for ``allowed_dir`` (creating it if needed).
98
+
99
+ Best-effort: never raises. Returns None if the directory cannot be ensured,
100
+ in which case the pointer is simply not consulted/updated.
101
+ """
102
+ if not allowed_dir:
103
+ return None
104
+ store = os.path.join(os.path.abspath(allowed_dir), SKILLS_DIRNAME)
105
+ try:
106
+ os.makedirs(store, exist_ok=True)
107
+ return store
108
+ except OSError as e:
109
+ ui.console.note(f"Could not use {store} for the latest-session pointer: {e}", "warn")
110
+ return None
111
+
112
+
113
+ def save_last_session_pointer(allowed_dir, session_dir):
114
+ """Record ``session_dir`` as the latest session in ``allowed_dir/.mini``.
115
+
116
+ Writes a tiny JSON file (the absolute path plus a timestamp) so a later
117
+ --load-last-session run can resume the most recent session without scanning
118
+ the cache dir. Best-effort: never raises, returns the written path or None.
119
+ """
120
+ if not session_dir:
121
+ return None
122
+ store = _last_session_dir_store(allowed_dir)
123
+ if store is None:
124
+ return None
125
+ payload = {
126
+ "session_dir": os.path.abspath(session_dir),
127
+ "recorded_at": time.time(),
128
+ }
129
+ path = os.path.join(store, LAST_SESSION_FILENAME)
130
+ try:
131
+ with open(path, "w", encoding="utf-8") as f:
132
+ json.dump(payload, f, indent=2)
133
+ return path
134
+ except OSError as e:
135
+ ui.console.note(f"Could not record latest-session pointer: {e}", "warn")
136
+ return None
137
+
138
+
139
+ def load_last_session_pointer(allowed_dir):
140
+ """Read the latest-session pointer recorded under ``allowed_dir/.mini``.
141
+
142
+ Returns the recorded session directory (a path into the cache folder), or
143
+ None if no pointer exists or it cannot be read. Best-effort: a missing or
144
+ malformed pointer simply yields None so the caller falls back to the
145
+ timestamp heuristic.
146
+ """
147
+ if not allowed_dir:
148
+ return None
149
+ store = os.path.join(os.path.abspath(allowed_dir), SKILLS_DIRNAME)
150
+ path = os.path.join(store, LAST_SESSION_FILENAME)
151
+ if not os.path.isfile(path):
152
+ return None
153
+ try:
154
+ with open(path, "r", encoding="utf-8") as f:
155
+ payload = json.load(f)
156
+ except Exception:
157
+ return None
158
+ if not isinstance(payload, dict):
159
+ return None
160
+ session_dir = payload.get("session_dir")
161
+ if not session_dir or not os.path.isdir(session_dir):
162
+ return None
163
+ return session_dir
164
+
165
+
166
+ def find_last_session_dir(cache_dir=None, allowed_dir=None, use_pointer=True):
167
+ """Return the path to the most recently *active* session dir.
168
+
169
+ When ``use_pointer`` and an ``allowed_dir`` are given, the .mini pointer
170
+ recorded by a previous run (see save_last_session_pointer) is consulted
171
+ *first* -- it is an explicit record of the session the user last worked in.
172
+ Only if that yields nothing do we fall back to the timestamp heuristic over
173
+ ``cache_dir`` (find_last_session_dir_by_mtime). Session folders with no
174
+ files are ignored. Never raises: a missing/unreadable cache dir simply
175
+ yields None.
176
+ """
177
+ if use_pointer and allowed_dir:
178
+ pointed = load_last_session_pointer(allowed_dir)
179
+ if pointed:
180
+ return pointed
181
+ return find_last_session_dir_by_mtime(cache_dir)
182
+
183
+
184
+ def find_last_session_dir_by_mtime(cache_dir):
185
+ """Timestamp heuristic for the most recently *active* session dir.
81
186
 
82
187
  "Most recent" is decided by the newest modification time of any file inside
83
188
  each session folder (a session rewrites its ``history_{n}.json`` every step),
mini_code_cli/ui.py CHANGED
@@ -371,6 +371,16 @@ class Console:
371
371
  """
372
372
  self.emit(self.style("-" * self.width, fg="gray", dim=True))
373
373
 
374
+ def short_divider(self, char="-", width=8, fg="gray"):
375
+ """Render a short horizontal delimiter line (not the full width).
376
+
377
+ Used to set off the user's own "You:" turn from the preceding assistant
378
+ reply with a light, unobtrusive rule rather than a full-width divider.
379
+ Defaults to a plain ASCII dash so it renders consistently regardless of
380
+ the console's unicode setting.
381
+ """
382
+ self.emit(self.style(char * width, fg=fg, dim=True))
383
+
374
384
  def clear_screen(self):
375
385
  """Clear the terminal and move the cursor to the top-left.
376
386
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-code-cli
3
- Version: 0.1.0.1
3
+ Version: 0.1.0.2
4
4
  Summary: Mini Coding Agent. Allows you to easily experiment with a small coding agent.
5
5
  Author-email: Nikolai Rozanov <nikolai.rozanov@gmail.com>
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -720,7 +720,7 @@ mini-code --enable-shell --auto-mode --agent-md "./skill.md" --allowed-dir "./"
720
720
  - allowed-dir: this specifies which dirs the agent can read and write from, that does not stop the shell comands! (though dangerous shell commands are still blocked by the classifier above).
721
721
  - ask-permission: asks user permission before every tool call. For shell commands, commands in the ALLOW tier skip the prompt, DENY is blocked, and ASK always prompts.
722
722
  - system-prompt: you can override the default prompt by using a string. (Note: Agent.md is always appended after the system prompt.)
723
- - load-last-session: instead of starting a fresh conversation, automatically continue your most recently active session (the one whose history was last modified) from the default cache dir. It resumes under a brand-new session id, so the original session is never overwritten. An explicit `--restore-session` (a specific path) takes precedence if both are given.
723
+ - load-last-session: instead of starting a fresh conversation, automatically continue your most recently active session from the default cache dir. It resumes under a brand-new session id, so the original session is never overwritten. An explicit `--restore-session` (a specific path) takes precedence if both are given. The "most recent" session is resolved in two steps: at the end of every interactive session, its path is recorded in a tiny `.mini/last_session.json` pointer inside the `--allowed-dir`, and `--load-last-session` consults that pointer first; only if there is no pointer (e.g. a fresh allowed dir, or the `--skills` feature disabled) does it fall back to the timestamp heuristic (the session whose history was modified most recently). The `.mini` pointer is the same directory the `--skills` feature uses, but it is a single dedicated file, so the two never clash.
724
724
  - plan: `--plan` enables **plan mode**. The agent now keeps two tracking files in the session directory: `CURRENT_GOAL.md` (the user's latest instruction) and `CURRENT_PLAN.md` (its own step-by-step plan, which it must update as it works and mark finished once the goal is done). The system message gets an extra instruction telling the agent to do this. Plan mode also changes how history compaction works: after the history is compacted to a summary, the current `CURRENT_PLAN.md` and the user's last instruction (`CURRENT_GOAL.md`) are re-injected into the messages so the agent keeps working against the same goal. When the session ends, the active goal is marked finished in `CURRENT_PLAN.md`.
725
725
 
726
726
  ### Available tools
@@ -1,8 +1,8 @@
1
1
  mini_code_cli/__init__.py,sha256=iNgHmKSHOuH99obGjnUMoS2JP4Gw4AOrIfu68EYaZQM,77
2
- mini_code_cli/agent_loop.py,sha256=jQEzWknHid0rKA8-vzES1RONg4tVJ8l4ImBleBC_RDE,58124
2
+ mini_code_cli/agent_loop.py,sha256=mU0IW3zekmgnnT7kXcjKL0I4F0dkBMpCpSBRo6JSkL0,59433
3
3
  mini_code_cli/config.py,sha256=AvzDZAKynQbuj5lcTtL-DcUwhyLa99QmNrLK6fYpdOA,814
4
4
  mini_code_cli/permissions.py,sha256=uQpQlUtQyVICpjUqdh9QQ_mgl51hZrGLHJzQR9vG124,2017
5
- mini_code_cli/persistence.py,sha256=L47NhXs6R54KETd8R17oGGxCXNEf7bjmvlwAJqtspLI,9944
5
+ mini_code_cli/persistence.py,sha256=BxUL4x8rha8ic_1vkWJu1cAFTuYyhK85ZdFVD9aFRYQ,14055
6
6
  mini_code_cli/plan.py,sha256=M5WZQuiN8F_K7zyWIrU_5R9L8dcN9k8iYouXzfL_KOs,6985
7
7
  mini_code_cli/prompts.py,sha256=X05Krwf9X46WinbHzHj8WYq4RTnO56KZpMiRYS2CGLw,5452
8
8
  mini_code_cli/runner.py,sha256=oYUAzbQOrkakuR2uT1OnIrruh_XJYWEDzKpM09T4Zp0,1927
@@ -10,14 +10,14 @@ mini_code_cli/sandbox.py,sha256=t7xS_AIpE8fbo5kj9zbUFbfEaAnq6iUE7plQcn6-uAk,2128
10
10
  mini_code_cli/shell_safety_utils.py,sha256=5E126vGnG7iIuTZe_R5JrxB_zxZpMKGe_pilz_Z2r4w,5551
11
11
  mini_code_cli/skills.py,sha256=baaz0RkPQ-OgWWwH5sHsKBmJdR430yT4HK3VLX0_88Y,13704
12
12
  mini_code_cli/tools.py,sha256=bW1zYyIj6OFp5TIcg5DCwheN-4xI1LQmDOQaHIfRV7M,10998
13
- mini_code_cli/ui.py,sha256=sNfjwxLWtRF3C3VpSBP9L0uncVgFhZmGquw8u9O956U,32127
13
+ mini_code_cli/ui.py,sha256=064mcoxrKSaC0eWTWqeM6urPy5MD7hE5Tr-z2jjnqO4,32613
14
14
  mini_code_cli/utils.py,sha256=ffUAIisO8_h7VvlwAOLhdoNSAIdssGdtQHXm-i8bcLE,4620
15
15
  mini_code_cli/subagents/__init__.py,sha256=3fgcwY0pNq5iW29OxLPtSA3H5IKXhl2LUhwbBjLUDuU,1884
16
16
  mini_code_cli/subagents/base.py,sha256=bhZEENoTVjebfqaEKS7uPWeU0SwQWFN3BD40EtziPbg,12660
17
17
  mini_code_cli/subagents/builtin.py,sha256=ENzuooXIylzfZZ8uYkZmdxgCrCQ4pdbfrOONkBdGSPg,7598
18
- mini_code_cli-0.1.0.1.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
19
- mini_code_cli-0.1.0.1.dist-info/METADATA,sha256=Er_McRLgRQ41CpufVZSHZeN1ZjBTGpf_s-odKMijoCE,52757
20
- mini_code_cli-0.1.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
21
- mini_code_cli-0.1.0.1.dist-info/entry_points.txt,sha256=MwDp5UbXpI7wodSmOSYL8_nKutnBiyp96ZDDtaX7x14,60
22
- mini_code_cli-0.1.0.1.dist-info/top_level.txt,sha256=3Hb6PpNkJYXvzDKXuuFJRvzCdsTRh0TuRWz-ScJ6qV0,14
23
- mini_code_cli-0.1.0.1.dist-info/RECORD,,
18
+ mini_code_cli-0.1.0.2.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
19
+ mini_code_cli-0.1.0.2.dist-info/METADATA,sha256=WRYRunHU0Swx0qpojq_JRynzzgjN8unAYLQ6E1Ppcf0,53278
20
+ mini_code_cli-0.1.0.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
21
+ mini_code_cli-0.1.0.2.dist-info/entry_points.txt,sha256=MwDp5UbXpI7wodSmOSYL8_nKutnBiyp96ZDDtaX7x14,60
22
+ mini_code_cli-0.1.0.2.dist-info/top_level.txt,sha256=3Hb6PpNkJYXvzDKXuuFJRvzCdsTRh0TuRWz-ScJ6qV0,14
23
+ mini_code_cli-0.1.0.2.dist-info/RECORD,,