nanocode-cli 0.5.10__tar.gz → 0.5.12__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.5.10
3
+ Version: 0.5.12
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
@@ -22,7 +22,7 @@ Requires-Python: >=3.11
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: anthropic>=0.64.0
25
- Requires-Dist: code-symbol-index>=0.1.13
25
+ Requires-Dist: code-symbol-index>=0.3.0
26
26
  Requires-Dist: json-repair
27
27
  Requires-Dist: openai>=2.37.0
28
28
  Requires-Dist: prompt-toolkit>=3.0
@@ -39,6 +39,8 @@ A small terminal coding agent written in Python.
39
39
 
40
40
  [简体中文](README.zh-CN.md)
41
41
 
42
+ [Chinese blog](https://hit9.dev/post/nanocode)
43
+
42
44
  nanocode is pre-1.0 software. Commands, configuration, and tool behavior may change before a stable release.
43
45
 
44
46
  ![nanocode screenshot](snapshots/nanocode-snapshot.png)
@@ -48,7 +50,7 @@ nanocode is pre-1.0 software. Commands, configuration, and tool behavior may cha
48
50
  - **Live turn control**: Add follow-up input while the agent is still working, without losing the current tool flow.
49
51
  - **File-state brain**: Reads and edits build a current, line-numbered view of the files that matter now.
50
52
  - **Stale-edit protection**: `line:hash` anchors reject edits when the target code has drifted.
51
- - **Project-aware navigation**: Use the symbol index to jump through outlines, references, and changed files quickly.
53
+ - **Project-aware navigation**: Use the symbol index to jump through outlines, references, implementors, call chains, and changed files quickly.
52
54
  - **Recoverable context**: Tool output stays bounded in the prompt, while raw `tr.N` results remain recallable.
53
55
  - **Cache-aware context**: Stable sections stay early and noisy working state stays late to improve prompt-cache reuse.
54
56
  - **Focused working memory**: `Note` separates goal, plan, and known facts from noisy execution logs.
@@ -138,6 +140,16 @@ Main fields:
138
140
 
139
141
  `api = "auto"` chooses between Chat Completions and Anthropic Messages using provider/model profiles. `prompt_cache_key = "auto"` derives a stable key from provider, model, workspace, and tool schema names.
140
142
 
143
+
144
+ ## Tested Providers
145
+
146
+ The following providers have been tested with nanocode:
147
+
148
+ - **deepseek**: DeepSeek API
149
+ - **opencode**: OpenCode API
150
+ - **aliyun**: Alibaba Cloud (Tongyi Qianwen) API via Chat Completions
151
+ - **llama.cpp**: Local inference via llama.cpp server
152
+
141
153
  ## Context Design
142
154
 
143
155
  Each model request is built manually from explicit messages. Stable context comes first, conversation stays as messages, working memory follows, and the latest file state is appended at the end.
@@ -4,6 +4,8 @@ A small terminal coding agent written in Python.
4
4
 
5
5
  [简体中文](README.zh-CN.md)
6
6
 
7
+ [Chinese blog](https://hit9.dev/post/nanocode)
8
+
7
9
  nanocode is pre-1.0 software. Commands, configuration, and tool behavior may change before a stable release.
8
10
 
9
11
  ![nanocode screenshot](snapshots/nanocode-snapshot.png)
@@ -13,7 +15,7 @@ nanocode is pre-1.0 software. Commands, configuration, and tool behavior may cha
13
15
  - **Live turn control**: Add follow-up input while the agent is still working, without losing the current tool flow.
14
16
  - **File-state brain**: Reads and edits build a current, line-numbered view of the files that matter now.
15
17
  - **Stale-edit protection**: `line:hash` anchors reject edits when the target code has drifted.
16
- - **Project-aware navigation**: Use the symbol index to jump through outlines, references, and changed files quickly.
18
+ - **Project-aware navigation**: Use the symbol index to jump through outlines, references, implementors, call chains, and changed files quickly.
17
19
  - **Recoverable context**: Tool output stays bounded in the prompt, while raw `tr.N` results remain recallable.
18
20
  - **Cache-aware context**: Stable sections stay early and noisy working state stays late to improve prompt-cache reuse.
19
21
  - **Focused working memory**: `Note` separates goal, plan, and known facts from noisy execution logs.
@@ -103,6 +105,16 @@ Main fields:
103
105
 
104
106
  `api = "auto"` chooses between Chat Completions and Anthropic Messages using provider/model profiles. `prompt_cache_key = "auto"` derives a stable key from provider, model, workspace, and tool schema names.
105
107
 
108
+
109
+ ## Tested Providers
110
+
111
+ The following providers have been tested with nanocode:
112
+
113
+ - **deepseek**: DeepSeek API
114
+ - **opencode**: OpenCode API
115
+ - **aliyun**: Alibaba Cloud (Tongyi Qianwen) API via Chat Completions
116
+ - **llama.cpp**: Local inference via llama.cpp server
117
+
106
118
  ## Context Design
107
119
 
108
120
  Each model request is built manually from explicit messages. Stable context comes first, conversation stays as messages, working memory follows, and the latest file state is appended at the end.
@@ -4,6 +4,8 @@
4
4
 
5
5
  [English](README.md)
6
6
 
7
+ [中文博客](https://hit9.dev/post/nanocode)
8
+
7
9
  nanocode 仍是 1.0 前的软件。稳定版发布前,命令、配置和工具行为都可能变化。
8
10
 
9
11
  ![nanocode screenshot](snapshots/nanocode-snapshot.png)
@@ -103,6 +105,15 @@ nanocode --init-config
103
105
 
104
106
  `api = "auto"` 会根据 provider/model profile 在 Chat Completions 和 Anthropic Messages 之间选择。`prompt_cache_key = "auto"` 会根据 provider、model、workspace 和工具 schema 名称生成稳定 key。
105
107
 
108
+
109
+ ## 已测试的 Provider
110
+
111
+ 以下 provider 已在 nanocode 中测试通过:
112
+
113
+ - **deepseek**: DeepSeek API
114
+ - **opencode**: OpenCode API
115
+ - **aliyun**: 阿里云通义千问 API(Chat Completions)
116
+ - **llama.cpp**: 通过 llama.cpp 服务端本地推理
106
117
  ## 上下文设计
107
118
 
108
119
  每次模型请求都由 nanocode 手动构建成明确的 messages。稳定上下文在前,会话作为 messages 保留,工作记忆随后,最新文件状态放在末尾。
@@ -55,7 +55,7 @@ from prompt_toolkit.widgets import SearchToolbar
55
55
  from rich.console import Console
56
56
  from rich.markdown import Markdown
57
57
 
58
- __version__ = "0.5.10"
58
+ __version__ = "0.5.12"
59
59
 
60
60
  Json = dict[str, Any]
61
61
  HTTP_USER_AGENT = "nanocode/" + __version__
@@ -1447,31 +1447,44 @@ class InspectCodeTool(Tool):
1447
1447
  NAME = "InspectCode"
1448
1448
  MAX_LIMIT: ClassVar[int] = 80
1449
1449
  MAX_OUTLINE_LIMIT: ClassVar[int] = 1000
1450
- DESCRIPTION = "Use the code index: find returns symbols, inspect returns anchors/members/references, outline returns a file symbol tree."
1451
- SIGNATURE = "InspectCode(mode,target,kind?,path?,symbol?,limit?,exact_only?)"
1450
+ MAX_DEPTH: ClassVar[int] = 5
1451
+ MODES: ClassVar[tuple[str, ...]] = ("find", "inspect", "outline", "refs", "impls", "callers", "callees")
1452
+ SYMBOL_MODES: ClassVar[frozenset[str]] = frozenset({"find", "inspect", "refs", "impls", "callers", "callees"})
1453
+ RESOLVE_MODES: ClassVar[frozenset[str]] = frozenset({"inspect", "refs", "impls", "callers", "callees"})
1454
+ CHAIN_MODES: ClassVar[frozenset[str]] = frozenset({"callers", "callees"})
1455
+ OPTION_KEYS: ClassVar[tuple[str, ...]] = ("limit", "kind", "path", "symbol", "exact_only", "depth", "offset", "all_kinds", "ref_kind", "loose")
1456
+ DESCRIPTION = "Use the code index: find returns symbols; inspect returns anchors/members/references; outline returns a file symbol tree; refs lists classified references; impls lists implementors; callers/callees walk the call chain."
1457
+ SIGNATURE = "InspectCode(mode,target,kind?,path?,symbol?,limit?,exact_only?,depth?,offset?,all_kinds?,ref_kind?,loose?)"
1452
1458
  EXAMPLE = (
1453
1459
  'Find symbols; kind can be class|function|method|variable|constant|enum|struct|interface|module|type|trait|field|property|impl|namespace|dict_key, comma-ok. Example: {"mode":"find","target":"Tool","kind":"class,function","limit":20}',
1454
- 'Find exact symbol in a path. Example: {"mode":"find","target":"Session","path":"src","exact_only":true,"limit":10}',
1455
1460
  'Inspect one symbol; path narrows candidates. Example: {"mode":"inspect","target":"Tool","path":"src/app.py"}',
1456
1461
  'Outline one file; symbol narrows subtree. Example: {"mode":"outline","target":"src/app.py","symbol":"App","limit":300}',
1462
+ 'List references; default hides import/attribute noise, ref_kind filters to call|read|write|inherit|type|import|attribute|usage (comma-ok), all_kinds shows everything, offset pages. Example: {"mode":"refs","target":"Tool","ref_kind":"call,write","offset":0}',
1463
+ 'List implementors of an interface/base. Example: {"mode":"impls","target":"Tool","kind":"class"}',
1464
+ 'Walk transitive callers/callees up to depth; callees loose includes ambiguous cross-module matches. Example: {"mode":"callers","target":"handle_job","depth":3}',
1457
1465
  )
1458
1466
 
1459
1467
  @classmethod
1460
1468
  def params_schema(cls) -> Json:
1461
1469
  props = {
1462
- "mode": {"type": "string", "enum": ["find", "inspect", "outline"]},
1470
+ "mode": {"type": "string", "enum": list(cls.MODES)},
1463
1471
  "target": {"type": "string"},
1464
1472
  "limit": {"type": "integer", "minimum": 1, "maximum": cls.MAX_OUTLINE_LIMIT},
1465
1473
  "kind": {"type": "string"},
1466
1474
  "path": {"type": "string"},
1467
1475
  "symbol": {"type": "string"},
1468
1476
  "exact_only": {"type": "boolean"},
1477
+ "depth": {"type": "integer", "minimum": 1, "maximum": cls.MAX_DEPTH},
1478
+ "offset": {"type": "integer", "minimum": 0},
1479
+ "all_kinds": {"type": "boolean"},
1480
+ "ref_kind": {"type": "string"},
1481
+ "loose": {"type": "boolean"},
1469
1482
  }
1470
1483
  return {"type": "object", "properties": props, "required": ["mode", "target"], "additionalProperties": False}
1471
1484
 
1472
1485
  @classmethod
1473
1486
  def payload_args(cls, payload: Json) -> list[Any]:
1474
- options = {key: payload[key] for key in ("limit", "kind", "path", "symbol", "exact_only") if key in payload}
1487
+ options = {key: payload[key] for key in cls.OPTION_KEYS if key in payload}
1475
1488
  return [str(payload.get("mode") or ""), str(payload.get("target") or ""), *([options] if options else [])]
1476
1489
 
1477
1490
  def call(self) -> str:
@@ -1483,22 +1496,37 @@ class InspectCodeTool(Tool):
1483
1496
  if len(self.args) == 3 and not isinstance(self.args[2], dict):
1484
1497
  raise ToolError("InspectCode options must be an object")
1485
1498
  options = self.args[2] if len(self.args) == 3 else {}
1486
- if unexpected := sorted(set(options) - {"limit", "kind", "path", "symbol", "exact_only"}):
1499
+ if unexpected := sorted(set(options) - set(self.OPTION_KEYS)):
1487
1500
  raise ToolError("InspectCode unexpected option: " + ", ".join(unexpected))
1488
- if mode not in {"find", "inspect", "outline"}:
1489
- raise ToolError("InspectCode mode must be find, inspect, or outline")
1501
+ if mode not in self.MODES:
1502
+ raise ToolError("InspectCode mode must be one of: " + ", ".join(self.MODES))
1490
1503
  if not target:
1491
1504
  raise ToolError("InspectCode target is required")
1492
- if mode in {"find", "inspect"} and re.search(r"\s", target):
1505
+ if mode in self.SYMBOL_MODES and re.search(r"\s", target):
1493
1506
  raise ToolError("InspectCode symbol target must not contain whitespace")
1494
- if mode == "inspect" and (target.endswith(".py") or os.path.exists(self.session.resolve_path(target))):
1495
- raise ToolError("InspectCode inspect target must be a symbol, not a file")
1507
+ if mode in self.RESOLVE_MODES and (target.endswith(".py") or os.path.exists(self.session.resolve_path(target))):
1508
+ raise ToolError(f"InspectCode {mode} target must be a symbol, not a file")
1496
1509
  if mode == "outline" and not os.path.isfile(self.session.resolve_path(target)):
1497
1510
  raise ToolError("InspectCode outline target must be an existing file")
1498
1511
  limit = options.get("limit")
1499
1512
  max_limit = self.MAX_OUTLINE_LIMIT if mode == "outline" else self.MAX_LIMIT
1500
1513
  if limit is not None and (isinstance(limit, bool) or not isinstance(limit, int) or limit < 1 or limit > max_limit):
1501
1514
  raise ToolError(f"InspectCode {mode} limit must be 1..{max_limit}")
1515
+ depth = options.get("depth")
1516
+ if depth is not None and (isinstance(depth, bool) or not isinstance(depth, int) or depth < 1 or depth > self.MAX_DEPTH):
1517
+ raise ToolError(f"InspectCode depth must be 1..{self.MAX_DEPTH}")
1518
+ offset = options.get("offset")
1519
+ if offset is not None and (isinstance(offset, bool) or not isinstance(offset, int) or offset < 0):
1520
+ raise ToolError("InspectCode offset must be >= 0")
1521
+ ref_kind = options.get("ref_kind")
1522
+ if ref_kind is not None:
1523
+ if not isinstance(ref_kind, str):
1524
+ raise ToolError("InspectCode ref_kind must be a string")
1525
+ if options.get("all_kinds"):
1526
+ raise ToolError("InspectCode ref_kind and all_kinds are mutually exclusive")
1527
+ tokens = [token.strip() for token in ref_kind.split(",") if token.strip()]
1528
+ if unknown := sorted(set(tokens) - csi.REFERENCE_KINDS):
1529
+ raise ToolError("InspectCode unknown ref_kind: " + ", ".join(unknown) + "; valid: " + ", ".join(sorted(csi.REFERENCE_KINDS)))
1502
1530
  index = CodeIndex(self.session)
1503
1531
  if not index.available():
1504
1532
  raise ToolError("code index is not available; run /index")
@@ -1520,6 +1548,16 @@ class InspectCodeTool(Tool):
1520
1548
  return csi.search(target, limit=limit or csi.DEFAULT_SEARCH_LIMIT, **common)
1521
1549
  if mode == "inspect":
1522
1550
  return csi.inspect(target, limit=limit or csi.DEFAULT_PAGE_LIMIT, anchors=True, **common)
1551
+ if mode == "refs":
1552
+ ref_kinds = options.get("ref_kind") or ("all" if options.get("all_kinds") else "behavioral")
1553
+ return csi.refs(target, limit=limit or csi.DEFAULT_MAX_REFERENCES, offset=int(options.get("offset") or 0), ref_kinds=ref_kinds, **common)
1554
+ if mode == "impls":
1555
+ return csi.impls(target, limit=limit or csi.DEFAULT_MAX_IMPLEMENTORS, offset=int(options.get("offset") or 0), **common)
1556
+ if mode in self.CHAIN_MODES:
1557
+ depth = int(options.get("depth") or 3)
1558
+ if mode == "callees":
1559
+ return csi.callees(target, limit=limit or csi.DEFAULT_MAX_CALLEES, depth=depth, loose=bool(options.get("loose")), **common)
1560
+ return csi.callers(target, limit=limit or csi.DEFAULT_MAX_CALLERS, depth=depth, **common)
1523
1561
  symbol = options.get("symbol") or None
1524
1562
  return csi.outline(
1525
1563
  target, root=self.session.cwd, symbol=str(symbol) if symbol else None, max_symbols=limit or csi.DEFAULT_MAX_OUTLINE_SYMBOLS, format="text"
@@ -4117,8 +4155,7 @@ Tools:
4117
4155
  except (EOFError, KeyboardInterrupt):
4118
4156
  pass
4119
4157
  finally:
4120
- if not stop_event.is_set():
4121
- self.queue_input_text = buffer.text
4158
+ self.queue_input_text = buffer.text
4122
4159
  self.queue_input_active.clear()
4123
4160
  if self.queue_input_app is app:
4124
4161
  self.queue_input_app = None
@@ -4137,7 +4174,8 @@ Tools:
4137
4174
  UpdateChecker(self.session).start()
4138
4175
  while True:
4139
4176
  try:
4140
- user_input = self.read_input()
4177
+ user_input = self.read_input(initial_text=self.queue_input_text)
4178
+ self.queue_input_text = ""
4141
4179
  except EOFError:
4142
4180
  self.emit("")
4143
4181
  return 0
@@ -4174,7 +4212,6 @@ Tools:
4174
4212
  if watcher:
4175
4213
  watcher.join(timeout=1.0)
4176
4214
  self.queue_input_paused.clear()
4177
- self.queue_input_text = ""
4178
4215
  self.session.state.manual_model_retry_requested = False
4179
4216
  CodeIndex(self.session).update_pending()
4180
4217
  self.status_bar.stop()
@@ -4233,6 +4270,7 @@ Tools:
4233
4270
  multiline: bool = False,
4234
4271
  submit_on_enter: bool = False,
4235
4272
  prompt_style: str = "class:prompt",
4273
+ initial_text: str = "",
4236
4274
  ) -> str:
4237
4275
  if self.input_history is None:
4238
4276
  return self.input_fn(prompt_text)
@@ -4248,6 +4286,7 @@ Tools:
4248
4286
  enable_history_search=True,
4249
4287
  multiline=multiline,
4250
4288
  accept_handler=accept,
4289
+ document=Document(initial_text, cursor_position=len(initial_text)),
4251
4290
  )
4252
4291
  search_toolbar = SearchToolbar()
4253
4292
  control = BufferControl(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nanocode-cli
3
- Version: 0.5.10
3
+ Version: 0.5.12
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
@@ -22,7 +22,7 @@ Requires-Python: >=3.11
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: anthropic>=0.64.0
25
- Requires-Dist: code-symbol-index>=0.1.13
25
+ Requires-Dist: code-symbol-index>=0.3.0
26
26
  Requires-Dist: json-repair
27
27
  Requires-Dist: openai>=2.37.0
28
28
  Requires-Dist: prompt-toolkit>=3.0
@@ -39,6 +39,8 @@ A small terminal coding agent written in Python.
39
39
 
40
40
  [简体中文](README.zh-CN.md)
41
41
 
42
+ [Chinese blog](https://hit9.dev/post/nanocode)
43
+
42
44
  nanocode is pre-1.0 software. Commands, configuration, and tool behavior may change before a stable release.
43
45
 
44
46
  ![nanocode screenshot](snapshots/nanocode-snapshot.png)
@@ -48,7 +50,7 @@ nanocode is pre-1.0 software. Commands, configuration, and tool behavior may cha
48
50
  - **Live turn control**: Add follow-up input while the agent is still working, without losing the current tool flow.
49
51
  - **File-state brain**: Reads and edits build a current, line-numbered view of the files that matter now.
50
52
  - **Stale-edit protection**: `line:hash` anchors reject edits when the target code has drifted.
51
- - **Project-aware navigation**: Use the symbol index to jump through outlines, references, and changed files quickly.
53
+ - **Project-aware navigation**: Use the symbol index to jump through outlines, references, implementors, call chains, and changed files quickly.
52
54
  - **Recoverable context**: Tool output stays bounded in the prompt, while raw `tr.N` results remain recallable.
53
55
  - **Cache-aware context**: Stable sections stay early and noisy working state stays late to improve prompt-cache reuse.
54
56
  - **Focused working memory**: `Note` separates goal, plan, and known facts from noisy execution logs.
@@ -138,6 +140,16 @@ Main fields:
138
140
 
139
141
  `api = "auto"` chooses between Chat Completions and Anthropic Messages using provider/model profiles. `prompt_cache_key = "auto"` derives a stable key from provider, model, workspace, and tool schema names.
140
142
 
143
+
144
+ ## Tested Providers
145
+
146
+ The following providers have been tested with nanocode:
147
+
148
+ - **deepseek**: DeepSeek API
149
+ - **opencode**: OpenCode API
150
+ - **aliyun**: Alibaba Cloud (Tongyi Qianwen) API via Chat Completions
151
+ - **llama.cpp**: Local inference via llama.cpp server
152
+
141
153
  ## Context Design
142
154
 
143
155
  Each model request is built manually from explicit messages. Stable context comes first, conversation stays as messages, working memory follows, and the latest file state is appended at the end.
@@ -1,5 +1,5 @@
1
1
  anthropic>=0.64.0
2
- code-symbol-index>=0.1.13
2
+ code-symbol-index>=0.3.0
3
3
  json-repair
4
4
  openai>=2.37.0
5
5
  prompt-toolkit>=3.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "nanocode-cli"
7
- version = "0.5.10"
7
+ version = "0.5.12"
8
8
  description = "A small terminal coding agent written in Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -28,7 +28,7 @@ classifiers = [
28
28
  ]
29
29
  dependencies = [
30
30
  "anthropic>=0.64.0",
31
- "code-symbol-index>=0.1.13",
31
+ "code-symbol-index>=0.3.0",
32
32
  "json-repair",
33
33
  "openai>=2.37.0",
34
34
  "prompt-toolkit>=3.0",
File without changes
File without changes
File without changes