nanocode-cli 0.8.0__tar.gz → 0.8.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nanocode-cli
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: A small terminal coding agent written in Python
5
5
  Author-email: hit9 <hit9@icloud.com>
6
6
  License-Expression: BSD-3-Clause
@@ -104,6 +104,7 @@ Useful arguments:
104
104
  - `--mcp <selector>`: choose which configured MCP servers to enable.
105
105
  - `--debug`: write model I/O debug traces.
106
106
  - `-v`, `--version`: show the version.
107
+ - `update` / `upgrade`: update nanocode to the latest release, using the right installer (uv tool, pipx, or pip) for how it was installed.
107
108
 
108
109
  During a running turn, type into the `+>` prompt to add follow-up input for the next model request.
109
110
 
@@ -67,6 +67,7 @@ Useful arguments:
67
67
  - `--mcp <selector>`: choose which configured MCP servers to enable.
68
68
  - `--debug`: write model I/O debug traces.
69
69
  - `-v`, `--version`: show the version.
70
+ - `update` / `upgrade`: update nanocode to the latest release, using the right installer (uv tool, pipx, or pip) for how it was installed.
70
71
 
71
72
  During a running turn, type into the `+>` prompt to add follow-up input for the next model request.
72
73
 
@@ -67,6 +67,7 @@ nanocode
67
67
  - `--mcp <selector>`:选择启用哪些已配置的 MCP 服务器。
68
68
  - `--debug`:写入模型 I/O debug trace。
69
69
  - `-v`, `--version`:显示版本。
70
+ - `update` / `upgrade`:将 nanocode 更新到最新版本,并根据安装方式(uv tool、pipx 或 pip)选择正确的升级方式。
70
71
 
71
72
  代理运行中,可以在 `+>` 提示符里输入追加内容,发送到下一次模型请求。
72
73
 
@@ -63,7 +63,7 @@ from rich.console import Console
63
63
  from rich.markdown import Markdown
64
64
  from rich.rule import Rule
65
65
 
66
- __version__ = "0.8.0"
66
+ __version__ = "0.8.2"
67
67
 
68
68
  Json = dict[str, Any]
69
69
  HTTP_USER_AGENT = "nanocode/" + __version__
@@ -7268,17 +7268,25 @@ Tools:
7268
7268
  while self.queue_input_active.is_set() and time.monotonic() < deadline:
7269
7269
  time.sleep(0.01)
7270
7270
 
7271
- def drain_queued_input(self) -> str:
7272
- """Return queued user input, or empty string if nothing queued."""
7273
- texts = []
7274
- if self.session.pending_user_inputs:
7275
- texts.extend(text for text in self.session.pending_user_inputs if text.strip())
7276
- self.session.pending_user_inputs.clear()
7277
- if self.queue_input_text.strip():
7278
- texts.append(self.queue_input_text)
7279
- self.queue_input_text = ""
7271
+ def take_entered_input(self) -> str:
7272
+ """Enter-committed queue input (pending_user_inputs), joined and cleared."""
7273
+ texts = [text for text in self.session.pending_user_inputs if text.strip()]
7274
+ self.session.pending_user_inputs.clear()
7280
7275
  return "\n".join(texts)
7281
7276
 
7277
+ def take_typed_input(self) -> str:
7278
+ """Un-entered text left in the +> box when the agent stopped, cleared."""
7279
+ typed = self.queue_input_text if self.queue_input_text.strip() else ""
7280
+ self.queue_input_text = ""
7281
+ return typed
7282
+
7283
+ def drain_queued_input(self) -> str:
7284
+ """Entered input first, then still-typed text (used for the headless combined path)."""
7285
+ return "\n".join(text for text in (self.take_entered_input(), self.take_typed_input()) if text)
7286
+
7287
+ def echo_input_line(self, text: str) -> None:
7288
+ print_formatted_text(FormattedText([("class:prompt", "nano> "), ("", text)]), style=self.style())
7289
+
7282
7290
  def run(self) -> int:
7283
7291
  self.emit(f"nanocode {__version__}. /help for commands.")
7284
7292
  if tip := self.startup_tip():
@@ -7291,8 +7299,19 @@ Tools:
7291
7299
  UpdateChecker(self.session).start()
7292
7300
  while True:
7293
7301
  try:
7294
- # Pre-fill any queued input into the prompt for review/edit instead of auto-submitting it.
7295
- user_input = self.read_input(initial_text=self.drain_queued_input())
7302
+ entered = self.take_entered_input()
7303
+ typed = self.take_typed_input()
7304
+ if entered and self.interactive_input:
7305
+ # Input you already pressed Enter on in the +> queue auto-submits as the next turn —
7306
+ # no second Enter. Any half-typed text goes back to the box for the following prompt.
7307
+ if typed:
7308
+ self.queue_input_text = typed
7309
+ self.echo_input_line(entered)
7310
+ user_input = entered
7311
+ else:
7312
+ # Headless (returns initial_text directly), or nothing entered: pre-fill the still-typed
7313
+ # text into the prompt for review/edit.
7314
+ user_input = self.read_input(initial_text="\n".join(text for text in (entered, typed) if text))
7296
7315
  except EOFError:
7297
7316
  self.emit("")
7298
7317
  self.save_and_emit_resume()
@@ -8480,6 +8499,68 @@ Tools:
8480
8499
  return "Set " + key
8481
8500
 
8482
8501
 
8502
+ class Updater:
8503
+ """Upgrade nanocode in place, choosing the command that matches how it was installed."""
8504
+
8505
+ PACKAGE = "nanocode-cli"
8506
+
8507
+ def run(self) -> int:
8508
+ try:
8509
+ latest = self.fetch_latest()
8510
+ except Exception as error:
8511
+ print("Error: failed to check latest version: " + Text.clean(str(error)), file=sys.stderr)
8512
+ return 1
8513
+ if not UpdateStatus(latest=latest).newer_than(__version__):
8514
+ print(f"nanocode {__version__} is already up to date (latest: {latest}).")
8515
+ return 0
8516
+ method, command = self.detect()
8517
+ if command is None:
8518
+ print(f"nanocode {__version__} -> {latest} available, but this is an {method} install.", file=sys.stderr)
8519
+ print("Update it the same way you installed it (e.g. git pull, or reinstall).", file=sys.stderr)
8520
+ return 1
8521
+ print(f"Updating nanocode {__version__} -> {latest} ({method}): {' '.join(command)}")
8522
+ try:
8523
+ result = subprocess.run(command)
8524
+ except Exception as error:
8525
+ print("Error: upgrade command failed: " + Text.clean(str(error)), file=sys.stderr)
8526
+ return 1
8527
+ if result.returncode != 0:
8528
+ print("Error: upgrade command exited with status " + str(result.returncode), file=sys.stderr)
8529
+ return result.returncode
8530
+ print(f"Updated nanocode to {latest}.")
8531
+ return 0
8532
+
8533
+ def fetch_latest(self) -> str:
8534
+ request = Request(UpdateChecker.PYPI_URL, headers={"Accept": "application/json", "User-Agent": HTTP_USER_AGENT})
8535
+ with urlopen(request, timeout=UpdateChecker.TIMEOUT) as response:
8536
+ data = json.loads(response.read().decode("utf-8", "replace"))
8537
+ version = data.get("info", {}).get("version") if isinstance(data, dict) else ""
8538
+ if not isinstance(version, str) or not UpdateStatus.version_tuple(version):
8539
+ raise NanocodeError("invalid PyPI version response")
8540
+ return version
8541
+
8542
+ def detect(self) -> tuple[str, list[str] | None]:
8543
+ """Return (method label, upgrade command). command is None when we cannot self-update."""
8544
+ if self.is_editable():
8545
+ return "editable", None
8546
+ location = os.path.realpath(os.path.dirname(__file__))
8547
+ if os.sep + "uv" + os.sep + "tools" + os.sep in location + os.sep and shutil.which("uv"):
8548
+ return "uv tool", ["uv", "tool", "upgrade", self.PACKAGE]
8549
+ if os.sep + "pipx" + os.sep in location + os.sep and shutil.which("pipx"):
8550
+ return "pipx", ["pipx", "upgrade", self.PACKAGE]
8551
+ return "pip", [sys.executable, "-m", "pip", "install", "--upgrade", self.PACKAGE]
8552
+
8553
+ @staticmethod
8554
+ def is_editable() -> bool:
8555
+ try:
8556
+ import importlib.metadata as metadata
8557
+
8558
+ raw = metadata.distribution(Updater.PACKAGE).read_text("direct_url.json")
8559
+ return bool(raw) and json.loads(raw).get("dir_info", {}).get("editable", False)
8560
+ except Exception:
8561
+ return False
8562
+
8563
+
8483
8564
  def main(argv: list[str] | None = None) -> int:
8484
8565
  parser = argparse.ArgumentParser(prog="nanocode")
8485
8566
  parser.add_argument("--config", default=None, help="Path to config TOML")
@@ -8489,10 +8570,13 @@ def main(argv: list[str] | None = None) -> int:
8489
8570
  parser.add_argument("--mcp", default="", help='Filter MCP servers, e.g. "orion*,!orionEval", "all", or "none"')
8490
8571
  parser.add_argument("--resume", default="", nargs="?", const="latest", help='Resume a session by UID, or "latest"/"last" for most recent')
8491
8572
  parser.add_argument("-v", "--version", action="store_true", help="Show version")
8573
+ parser.add_argument("command", nargs="?", choices=["update", "upgrade"], help="Update nanocode to the latest version")
8492
8574
  args = parser.parse_args(argv)
8493
8575
  if args.version:
8494
8576
  print(__version__)
8495
8577
  return 0
8578
+ if args.command in ("update", "upgrade"):
8579
+ return Updater().run()
8496
8580
  try:
8497
8581
  if args.init_config:
8498
8582
  path, created = ConfigFile.init(args.config)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nanocode-cli
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: A small terminal coding agent written in Python
5
5
  Author-email: hit9 <hit9@icloud.com>
6
6
  License-Expression: BSD-3-Clause
@@ -104,6 +104,7 @@ Useful arguments:
104
104
  - `--mcp <selector>`: choose which configured MCP servers to enable.
105
105
  - `--debug`: write model I/O debug traces.
106
106
  - `-v`, `--version`: show the version.
107
+ - `update` / `upgrade`: update nanocode to the latest release, using the right installer (uv tool, pipx, or pip) for how it was installed.
107
108
 
108
109
  During a running turn, type into the `+>` prompt to add follow-up input for the next model request.
109
110
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nanocode-cli"
7
- version = "0.8.0"
7
+ version = "0.8.2"
8
8
  description = "A small terminal coding agent written in Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
File without changes
File without changes
File without changes