yncli 1.0.9__tar.gz → 1.1.0__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.
- {yncli-1.0.9 → yncli-1.1.0}/PKG-INFO +1 -1
- {yncli-1.0.9 → yncli-1.1.0}/pyproject.toml +1 -1
- {yncli-1.0.9 → yncli-1.1.0}/setup.py +1 -1
- yncli-1.1.0/yncli/mention_resolver.py +88 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/tui.py +37 -1
- yncli-1.1.0/yncli/version.py +1 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli.egg-info/PKG-INFO +1 -1
- {yncli-1.0.9 → yncli-1.1.0}/yncli.egg-info/SOURCES.txt +1 -0
- yncli-1.0.9/yncli/version.py +0 -1
- {yncli-1.0.9 → yncli-1.1.0}/README.md +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/setup.cfg +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/__init__.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/agent.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/built_in_skills/architect.md +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/built_in_skills/code_review.md +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/built_in_skills/debug_oracle.md +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/built_in_skills/debugger.md +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/built_in_skills/polyglot.md +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/built_in_skills/refactor_master.md +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/built_in_skills/security_auditor.md +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/built_in_skills/system_architect.md +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/built_in_skills/test_runner.md +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/built_in_skills/ultrabrain.md +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/clean_text.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/client.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/config.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/language_detector.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/main.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/skills_manager.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/system_prompt.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/tools/__init__.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/tools/file_tools.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/tools/polyglot_tools.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/tools/search_tools.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/tools/system_tools.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/update_checker.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/workspace_memory.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli/workspace_scanner.py +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli.egg-info/dependency_links.txt +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli.egg-info/entry_points.txt +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli.egg-info/requires.txt +0 -0
- {yncli-1.0.9 → yncli-1.1.0}/yncli.egg-info/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ long_description = (this_directory / "README.md").read_text(encoding="utf-8") if
|
|
|
6
6
|
|
|
7
7
|
setup(
|
|
8
8
|
name="yncli",
|
|
9
|
-
version="1.0
|
|
9
|
+
version="1.1.0",
|
|
10
10
|
description="Autonomous Polyglot AI Coding Agent & TUI",
|
|
11
11
|
long_description=long_description,
|
|
12
12
|
long_description_content_type="text/markdown",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Tuple, List, Dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def resolve_file_mentions(prompt: str, workspace_dir: str = ".") -> Tuple[str, List[Dict[str, str]]]:
|
|
8
|
+
"""
|
|
9
|
+
Scans the prompt for @file or @folder mentions (e.g. @app/Http/Controllers/UserController.php or @index.html).
|
|
10
|
+
Loads the contents of referenced files and appends them as explicit contextual attachments.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
(cleaned_prompt, list_of_attached_files)
|
|
14
|
+
"""
|
|
15
|
+
cwd = Path(workspace_dir).resolve()
|
|
16
|
+
|
|
17
|
+
# Matches patterns like @filename.ext or @"path with spaces/file.ext" or @path/to/file
|
|
18
|
+
mention_pattern = re.compile(r'@(?:"([^"]+)"|([a-zA-Z0-9_\-\.\/\\]+))')
|
|
19
|
+
|
|
20
|
+
matches = mention_pattern.findall(prompt)
|
|
21
|
+
if not matches:
|
|
22
|
+
return prompt, []
|
|
23
|
+
|
|
24
|
+
attached_files = []
|
|
25
|
+
seen_paths = set()
|
|
26
|
+
|
|
27
|
+
for m_quoted, m_unquoted in matches:
|
|
28
|
+
rel_path = (m_quoted or m_unquoted).strip()
|
|
29
|
+
if not rel_path:
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
clean_path = rel_path.replace("\\", "/")
|
|
33
|
+
if clean_path in seen_paths:
|
|
34
|
+
continue
|
|
35
|
+
seen_paths.add(clean_path)
|
|
36
|
+
|
|
37
|
+
target_file = cwd / rel_path
|
|
38
|
+
if target_file.exists() and target_file.is_file():
|
|
39
|
+
try:
|
|
40
|
+
# Read content up to 100KB
|
|
41
|
+
size = target_file.stat().st_size
|
|
42
|
+
if size < 150000:
|
|
43
|
+
with open(target_file, "r", encoding="utf-8", errors="replace") as fp:
|
|
44
|
+
content = fp.read()
|
|
45
|
+
attached_files.append({
|
|
46
|
+
"path": clean_path,
|
|
47
|
+
"content": content,
|
|
48
|
+
"lines": len(content.splitlines()),
|
|
49
|
+
"size_bytes": size
|
|
50
|
+
})
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
elif not target_file.exists():
|
|
54
|
+
# Try searching in subdirectories
|
|
55
|
+
for root, _, files in os.walk(cwd):
|
|
56
|
+
for f in files:
|
|
57
|
+
if f.lower() == Path(rel_path).name.lower():
|
|
58
|
+
found = Path(root) / f
|
|
59
|
+
try:
|
|
60
|
+
rel_found = found.relative_to(cwd).as_posix()
|
|
61
|
+
if rel_found not in seen_paths and found.stat().st_size < 150000:
|
|
62
|
+
seen_paths.add(rel_found)
|
|
63
|
+
with open(found, "r", encoding="utf-8", errors="replace") as fp:
|
|
64
|
+
content = fp.read()
|
|
65
|
+
attached_files.append({
|
|
66
|
+
"path": rel_found,
|
|
67
|
+
"content": content,
|
|
68
|
+
"lines": len(content.splitlines()),
|
|
69
|
+
"size_bytes": found.stat().st_size
|
|
70
|
+
})
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
if not attached_files:
|
|
76
|
+
return prompt, []
|
|
77
|
+
|
|
78
|
+
# Build enriched context block
|
|
79
|
+
enrichment_blocks = ["\n\n### [USER ATTACHED @FILES CONTEXT]:"]
|
|
80
|
+
for item in attached_files:
|
|
81
|
+
enrichment_blocks.append(
|
|
82
|
+
f"\n--- ATTACHED FILE: @{item['path']} ({item['lines']} lines) ---\n"
|
|
83
|
+
f"{item['content']}\n"
|
|
84
|
+
f"--- END OF @{item['path']} ---"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
enriched_prompt = prompt + "\n" + "\n".join(enrichment_blocks)
|
|
88
|
+
return enriched_prompt, attached_files
|
|
@@ -50,6 +50,7 @@ class TerminalUI:
|
|
|
50
50
|
from prompt_toolkit.history import FileHistory
|
|
51
51
|
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
|
52
52
|
from prompt_toolkit.styles import Style
|
|
53
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
53
54
|
|
|
54
55
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
55
56
|
history_file = CONFIG_DIR / "history.txt"
|
|
@@ -57,11 +58,37 @@ class TerminalUI:
|
|
|
57
58
|
prompt_style = Style.from_dict({
|
|
58
59
|
"prompt": "#38bdf8",
|
|
59
60
|
"arrow": "#a855f7",
|
|
61
|
+
"completion-menu.completion": "bg:#1e1e2e #cdd6f4",
|
|
62
|
+
"completion-menu.completion.current": "bg:#313244 #89b4fa bold",
|
|
63
|
+
"completion-menu.meta": "bg:#181825 #a6adc8",
|
|
60
64
|
})
|
|
61
65
|
|
|
66
|
+
class MentionCompleter(Completer):
|
|
67
|
+
def __init__(self, workspace_memory):
|
|
68
|
+
self.workspace_memory = workspace_memory
|
|
69
|
+
|
|
70
|
+
def get_completions(self, document, complete_event):
|
|
71
|
+
text = document.text_before_cursor
|
|
72
|
+
# Check if user just typed '@' or is typing a path after '@'
|
|
73
|
+
if "@" in text:
|
|
74
|
+
last_at = text.rfind("@")
|
|
75
|
+
word = text[last_at + 1:]
|
|
76
|
+
# Offer completion for files in workspace tree
|
|
77
|
+
for fpath in self.workspace_memory.file_tree:
|
|
78
|
+
if fpath.lower().startswith(word.lower()) or word.lower() in fpath.lower():
|
|
79
|
+
yield Completion(
|
|
80
|
+
fpath,
|
|
81
|
+
start_position=-len(word),
|
|
82
|
+
display=f"@{fpath}",
|
|
83
|
+
display_meta="file"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
completer = MentionCompleter(self.agent.workspace_memory)
|
|
87
|
+
|
|
62
88
|
self.session = PromptSession(
|
|
63
89
|
history=FileHistory(str(history_file)),
|
|
64
90
|
auto_suggest=AutoSuggestFromHistory(),
|
|
91
|
+
completer=completer,
|
|
65
92
|
style=prompt_style
|
|
66
93
|
)
|
|
67
94
|
except Exception:
|
|
@@ -459,6 +486,15 @@ class TerminalUI:
|
|
|
459
486
|
is_thinking = False
|
|
460
487
|
step_history: List[str] = []
|
|
461
488
|
|
|
489
|
+
from yncli.mention_resolver import resolve_file_mentions
|
|
490
|
+
resolved_prompt, attached_files = resolve_file_mentions(user_prompt, workspace_dir=self.agent.workspace_dir)
|
|
491
|
+
|
|
492
|
+
if attached_files:
|
|
493
|
+
pills = []
|
|
494
|
+
for af in attached_files:
|
|
495
|
+
pills.append(f"[bold cyan]@{af['path']}[/bold cyan] [dim]({af['lines']} baris)[/dim]")
|
|
496
|
+
self.console.print(f"[dim]📎 Menyertakan file:[/dim] {', '.join(pills)}")
|
|
497
|
+
|
|
462
498
|
def _update_live_display(current_status: str) -> None:
|
|
463
499
|
# Maintain max 3 recent steps
|
|
464
500
|
display_lines = list(step_history[-2:]) + [f"[bold cyan]●[/bold cyan] {current_status}"]
|
|
@@ -525,7 +561,7 @@ class TerminalUI:
|
|
|
525
561
|
_update_live_display(f"[bold #38bdf8][ - ][/bold #38bdf8] Menganalisis instruksi ({self.agent.model})...")
|
|
526
562
|
try:
|
|
527
563
|
res = self.agent.run_turn(
|
|
528
|
-
user_prompt=
|
|
564
|
+
user_prompt=resolved_prompt,
|
|
529
565
|
on_thinking=on_thinking,
|
|
530
566
|
on_content=on_content,
|
|
531
567
|
on_tool_start=on_tool_start,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.1.0"
|
yncli-1.0.9/yncli/version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.0.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|