jarvis-ai-assistant 0.3.18__py3-none-any.whl → 0.3.20__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +30 -12
- jarvis/jarvis_agent/config_editor.py +1 -1
- jarvis/jarvis_agent/edit_file_handler.py +8 -13
- jarvis/jarvis_agent/memory_manager.py +4 -4
- jarvis/jarvis_agent/shell_input_handler.py +17 -2
- jarvis/jarvis_agent/task_analyzer.py +4 -3
- jarvis/jarvis_agent/task_manager.py +6 -6
- jarvis/jarvis_agent/tool_executor.py +2 -2
- jarvis/jarvis_code_agent/code_agent.py +21 -29
- jarvis/jarvis_code_analysis/code_review.py +2 -4
- jarvis/jarvis_git_utils/git_commiter.py +17 -18
- jarvis/jarvis_methodology/main.py +12 -12
- jarvis/jarvis_platform/ai8.py +0 -4
- jarvis/jarvis_platform/base.py +16 -15
- jarvis/jarvis_platform/kimi.py +13 -13
- jarvis/jarvis_platform/tongyi.py +17 -15
- jarvis/jarvis_platform/yuanbao.py +11 -11
- jarvis/jarvis_platform_manager/service.py +2 -2
- jarvis/jarvis_rag/cli.py +36 -32
- jarvis/jarvis_rag/embedding_manager.py +11 -6
- jarvis/jarvis_rag/llm_interface.py +6 -5
- jarvis/jarvis_rag/rag_pipeline.py +9 -8
- jarvis/jarvis_rag/reranker.py +3 -2
- jarvis/jarvis_rag/retriever.py +18 -8
- jarvis/jarvis_smart_shell/main.py +307 -47
- jarvis/jarvis_stats/cli.py +2 -1
- jarvis/jarvis_stats/stats.py +45 -5
- jarvis/jarvis_stats/storage.py +220 -9
- jarvis/jarvis_tools/clear_memory.py +0 -11
- jarvis/jarvis_tools/cli/main.py +18 -17
- jarvis/jarvis_tools/edit_file.py +4 -4
- jarvis/jarvis_tools/execute_script.py +5 -1
- jarvis/jarvis_tools/file_analyzer.py +6 -6
- jarvis/jarvis_tools/generate_new_tool.py +6 -17
- jarvis/jarvis_tools/read_code.py +3 -6
- jarvis/jarvis_tools/read_webpage.py +4 -4
- jarvis/jarvis_tools/registry.py +8 -28
- jarvis/jarvis_tools/retrieve_memory.py +5 -16
- jarvis/jarvis_tools/rewrite_file.py +0 -4
- jarvis/jarvis_tools/save_memory.py +2 -10
- jarvis/jarvis_tools/search_web.py +5 -8
- jarvis/jarvis_tools/virtual_tty.py +22 -40
- jarvis/jarvis_utils/clipboard.py +2 -2
- jarvis/jarvis_utils/input.py +316 -30
- jarvis/jarvis_utils/methodology.py +3 -3
- jarvis/jarvis_utils/output.py +215 -135
- jarvis/jarvis_utils/utils.py +35 -58
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.20.dist-info}/METADATA +1 -1
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.20.dist-info}/RECORD +54 -54
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.20.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.20.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.20.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.3.18.dist-info → jarvis_ai_assistant-0.3.20.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,7 @@ from typing import List, cast
|
|
3
3
|
from langchain_huggingface import HuggingFaceEmbeddings
|
4
4
|
|
5
5
|
from .cache import EmbeddingCache
|
6
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
6
7
|
|
7
8
|
|
8
9
|
class EmbeddingManager:
|
@@ -23,7 +24,7 @@ class EmbeddingManager:
|
|
23
24
|
"""
|
24
25
|
self.model_name = model_name
|
25
26
|
|
26
|
-
print(f"
|
27
|
+
PrettyOutput.print(f"初始化嵌入管理器, 模型: '{self.model_name}'...", OutputType.INFO)
|
27
28
|
|
28
29
|
# 缓存的salt是模型名称,以防止冲突
|
29
30
|
self.cache = EmbeddingCache(cache_dir=cache_dir, salt=self.model_name)
|
@@ -42,8 +43,8 @@ class EmbeddingManager:
|
|
42
43
|
show_progress=True,
|
43
44
|
)
|
44
45
|
except Exception as e:
|
45
|
-
print(f"
|
46
|
-
print("请确保您已安装 'sentence_transformers' 和 'torch'。")
|
46
|
+
PrettyOutput.print(f"加载嵌入模型 '{self.model_name}' 时出错: {e}", OutputType.ERROR)
|
47
|
+
PrettyOutput.print("请确保您已安装 'sentence_transformers' 和 'torch'。", OutputType.WARNING)
|
47
48
|
raise
|
48
49
|
|
49
50
|
def embed_documents(self, texts: List[str]) -> List[List[float]]:
|
@@ -71,8 +72,9 @@ class EmbeddingManager:
|
|
71
72
|
|
72
73
|
# 为不在缓存中的文本计算嵌入
|
73
74
|
if texts_to_embed:
|
74
|
-
print(
|
75
|
-
f"
|
75
|
+
PrettyOutput.print(
|
76
|
+
f"缓存未命中。正在为 {len(texts_to_embed)}/{len(texts)} 个文档计算嵌入。",
|
77
|
+
OutputType.INFO,
|
76
78
|
)
|
77
79
|
new_embeddings = self.model.embed_documents(texts_to_embed)
|
78
80
|
|
@@ -83,7 +85,10 @@ class EmbeddingManager:
|
|
83
85
|
for i, embedding in zip(indices_to_embed, new_embeddings):
|
84
86
|
cached_embeddings[i] = embedding
|
85
87
|
else:
|
86
|
-
print(
|
88
|
+
PrettyOutput.print(
|
89
|
+
f"缓存命中。所有 {len(texts)} 个文档的嵌入均从缓存中检索。",
|
90
|
+
OutputType.SUCCESS,
|
91
|
+
)
|
87
92
|
|
88
93
|
return cast(List[List[float]], cached_embeddings)
|
89
94
|
|
@@ -6,6 +6,7 @@ from abc import ABC, abstractmethod
|
|
6
6
|
from jarvis.jarvis_agent import Agent as JarvisAgent
|
7
7
|
from jarvis.jarvis_platform.base import BasePlatform
|
8
8
|
from jarvis.jarvis_platform.registry import PlatformRegistry
|
9
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
9
10
|
|
10
11
|
|
11
12
|
class LLMInterface(ABC):
|
@@ -41,7 +42,7 @@ class ToolAgent_LLM(LLMInterface):
|
|
41
42
|
"""
|
42
43
|
初始化工具-代理 LLM 包装器。
|
43
44
|
"""
|
44
|
-
print("
|
45
|
+
PrettyOutput.print("已初始化工具 Agent 作为最终应答者。", OutputType.INFO)
|
45
46
|
self.allowed_tools = ["read_code", "execute_script"]
|
46
47
|
# 为代理提供一个通用的系统提示
|
47
48
|
self.system_prompt = "You are a helpful assistant. Please answer the user's question based on the provided context. You can use tools to find more information if needed."
|
@@ -83,7 +84,7 @@ class ToolAgent_LLM(LLMInterface):
|
|
83
84
|
return str(final_answer)
|
84
85
|
|
85
86
|
except Exception as e:
|
86
|
-
print(f"
|
87
|
+
PrettyOutput.print(f"Agent 在执行过程中发生错误: {e}", OutputType.ERROR)
|
87
88
|
return "错误: Agent 未能成功生成回答。"
|
88
89
|
|
89
90
|
|
@@ -102,9 +103,9 @@ class JarvisPlatform_LLM(LLMInterface):
|
|
102
103
|
self.registry = PlatformRegistry.get_global_platform_registry()
|
103
104
|
self.platform: BasePlatform = self.registry.get_normal_platform()
|
104
105
|
self.platform.set_suppress_output(False) # 确保模型没有控制台输出
|
105
|
-
print(f"
|
106
|
+
PrettyOutput.print(f"已初始化 Jarvis 平台 LLM,模型: {self.platform.name()}", OutputType.INFO)
|
106
107
|
except Exception as e:
|
107
|
-
print(f"
|
108
|
+
PrettyOutput.print(f"初始化 Jarvis 平台 LLM 失败: {e}", OutputType.ERROR)
|
108
109
|
raise
|
109
110
|
|
110
111
|
def generate(self, prompt: str, **kwargs) -> str:
|
@@ -122,5 +123,5 @@ class JarvisPlatform_LLM(LLMInterface):
|
|
122
123
|
# 使用健壮的chat_until_success方法
|
123
124
|
return self.platform.chat_until_success(prompt)
|
124
125
|
except Exception as e:
|
125
|
-
print(f"
|
126
|
+
PrettyOutput.print(f"调用 Jarvis 平台模型时发生错误: {e}", OutputType.ERROR)
|
126
127
|
return "错误: 无法从本地LLM获取响应。"
|
@@ -8,6 +8,7 @@ from .llm_interface import JarvisPlatform_LLM, LLMInterface, ToolAgent_LLM
|
|
8
8
|
from .query_rewriter import QueryRewriter
|
9
9
|
from .reranker import Reranker
|
10
10
|
from .retriever import ChromaRetriever
|
11
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
11
12
|
from jarvis.jarvis_utils.config import (
|
12
13
|
get_rag_embedding_model,
|
13
14
|
get_rag_rerank_model,
|
@@ -74,7 +75,7 @@ class JarvisRAGPipeline:
|
|
74
75
|
self._reranker: Optional[Reranker] = None
|
75
76
|
self._query_rewriter: Optional[QueryRewriter] = None
|
76
77
|
|
77
|
-
print("
|
78
|
+
PrettyOutput.print("JarvisRAGPipeline 初始化成功 (模型按需加载).", OutputType.SUCCESS)
|
78
79
|
|
79
80
|
def _get_embedding_manager(self) -> EmbeddingManager:
|
80
81
|
if self._embedding_manager is None:
|
@@ -193,7 +194,7 @@ class JarvisRAGPipeline:
|
|
193
194
|
# 2. 为每个重写的查询检索初始候选文档
|
194
195
|
all_candidate_docs = []
|
195
196
|
for q in rewritten_queries:
|
196
|
-
print(f"
|
197
|
+
PrettyOutput.print(f"正在为查询变体 '{q}' 进行混合检索...", OutputType.INFO)
|
197
198
|
candidates = self._get_retriever().retrieve(
|
198
199
|
q, n_results=n_results * 2, use_bm25=self.use_bm25
|
199
200
|
)
|
@@ -208,7 +209,7 @@ class JarvisRAGPipeline:
|
|
208
209
|
|
209
210
|
# 3. 根据*原始*查询对统一的候选池进行重排
|
210
211
|
if self.use_rerank:
|
211
|
-
print(f"
|
212
|
+
PrettyOutput.print(f"正在对 {len(unique_candidate_docs)} 个候选文档进行重排(基于原始问题)...", OutputType.INFO)
|
212
213
|
retrieved_docs = self._get_reranker().rerank(
|
213
214
|
query_text, unique_candidate_docs, top_n=n_results
|
214
215
|
)
|
@@ -229,15 +230,15 @@ class JarvisRAGPipeline:
|
|
229
230
|
)
|
230
231
|
)
|
231
232
|
if sources:
|
232
|
-
print(
|
233
|
+
PrettyOutput.print("根据以下文档回答:", OutputType.INFO)
|
233
234
|
for source in sources:
|
234
|
-
print(f" - {source}")
|
235
|
+
PrettyOutput.print(f" - {source}", OutputType.INFO)
|
235
236
|
|
236
237
|
# 4. 创建最终提示并生成答案
|
237
238
|
# 我们使用原始的query_text作为给LLM的最终提示
|
238
239
|
prompt = self._create_prompt(query_text, retrieved_docs)
|
239
240
|
|
240
|
-
print("
|
241
|
+
PrettyOutput.print("正在从LLM生成答案...", OutputType.INFO)
|
241
242
|
answer = self.llm.generate(prompt)
|
242
243
|
|
243
244
|
return answer
|
@@ -259,7 +260,7 @@ class JarvisRAGPipeline:
|
|
259
260
|
# 2. 检索候选文档
|
260
261
|
all_candidate_docs = []
|
261
262
|
for q in rewritten_queries:
|
262
|
-
print(f"
|
263
|
+
PrettyOutput.print(f"正在为查询变体 '{q}' 进行混合检索...", OutputType.INFO)
|
263
264
|
candidates = self._get_retriever().retrieve(
|
264
265
|
q, n_results=n_results * 2, use_bm25=self.use_bm25
|
265
266
|
)
|
@@ -273,7 +274,7 @@ class JarvisRAGPipeline:
|
|
273
274
|
|
274
275
|
# 3. 重排
|
275
276
|
if self.use_rerank:
|
276
|
-
print(f"
|
277
|
+
PrettyOutput.print(f"正在对 {len(unique_candidate_docs)} 个候选文档进行重排...", OutputType.INFO)
|
277
278
|
retrieved_docs = self._get_reranker().rerank(
|
278
279
|
query_text, unique_candidate_docs, top_n=n_results
|
279
280
|
)
|
jarvis/jarvis_rag/reranker.py
CHANGED
@@ -4,6 +4,7 @@ from langchain.docstore.document import Document
|
|
4
4
|
from sentence_transformers.cross_encoder import ( # type: ignore
|
5
5
|
CrossEncoder,
|
6
6
|
)
|
7
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
7
8
|
|
8
9
|
|
9
10
|
class Reranker:
|
@@ -19,9 +20,9 @@ class Reranker:
|
|
19
20
|
参数:
|
20
21
|
model_name (str): 要使用的Cross-Encoder模型的名称。
|
21
22
|
"""
|
22
|
-
print(f"
|
23
|
+
PrettyOutput.print(f"正在初始化重排模型: {model_name}...", OutputType.INFO)
|
23
24
|
self.model = CrossEncoder(model_name)
|
24
|
-
print("
|
25
|
+
PrettyOutput.print("重排模型初始化成功。", OutputType.SUCCESS)
|
25
26
|
|
26
27
|
def rerank(
|
27
28
|
self, query: str, documents: List[Document], top_n: int = 5
|
jarvis/jarvis_rag/retriever.py
CHANGED
@@ -8,6 +8,7 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
|
|
8
8
|
from rank_bm25 import BM25Okapi # type: ignore
|
9
9
|
|
10
10
|
from .embedding_manager import EmbeddingManager
|
11
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
11
12
|
|
12
13
|
|
13
14
|
class ChromaRetriever:
|
@@ -39,7 +40,10 @@ class ChromaRetriever:
|
|
39
40
|
self.collection = self.client.get_or_create_collection(
|
40
41
|
name=self.collection_name
|
41
42
|
)
|
42
|
-
print(
|
43
|
+
PrettyOutput.print(
|
44
|
+
f"ChromaDB 客户端已在 '{db_path}' 初始化,集合为 '{collection_name}'。",
|
45
|
+
OutputType.SUCCESS,
|
46
|
+
)
|
43
47
|
|
44
48
|
# BM25索引设置
|
45
49
|
self.bm25_index_path = os.path.join(self.db_path, f"{collection_name}_bm25.pkl")
|
@@ -48,24 +52,24 @@ class ChromaRetriever:
|
|
48
52
|
def _load_or_initialize_bm25(self):
|
49
53
|
"""从磁盘加载BM25索引或初始化一个新索引。"""
|
50
54
|
if os.path.exists(self.bm25_index_path):
|
51
|
-
print("
|
55
|
+
PrettyOutput.print("正在加载现有的 BM25 索引...", OutputType.INFO)
|
52
56
|
with open(self.bm25_index_path, "rb") as f:
|
53
57
|
data = pickle.load(f)
|
54
58
|
self.bm25_corpus = data["corpus"]
|
55
59
|
self.bm25_index = BM25Okapi(self.bm25_corpus)
|
56
|
-
print("
|
60
|
+
PrettyOutput.print("BM25 索引加载成功。", OutputType.SUCCESS)
|
57
61
|
else:
|
58
|
-
print("
|
62
|
+
PrettyOutput.print("未找到 BM25 索引,将初始化一个新的。", OutputType.WARNING)
|
59
63
|
self.bm25_corpus = []
|
60
64
|
self.bm25_index = None
|
61
65
|
|
62
66
|
def _save_bm25_index(self):
|
63
67
|
"""将BM25索引保存到磁盘。"""
|
64
68
|
if self.bm25_index:
|
65
|
-
print("
|
69
|
+
PrettyOutput.print("正在保存 BM25 索引...", OutputType.INFO)
|
66
70
|
with open(self.bm25_index_path, "wb") as f:
|
67
71
|
pickle.dump({"corpus": self.bm25_corpus, "index": self.bm25_index}, f)
|
68
|
-
print("
|
72
|
+
PrettyOutput.print("BM25 索引保存成功。", OutputType.SUCCESS)
|
69
73
|
|
70
74
|
def add_documents(
|
71
75
|
self, documents: List[Document], chunk_size=1000, chunk_overlap=100
|
@@ -78,7 +82,10 @@ class ChromaRetriever:
|
|
78
82
|
)
|
79
83
|
chunks = text_splitter.split_documents(documents)
|
80
84
|
|
81
|
-
print(
|
85
|
+
PrettyOutput.print(
|
86
|
+
f"已将 {len(documents)} 个文档拆分为 {len(chunks)} 个块。",
|
87
|
+
OutputType.INFO,
|
88
|
+
)
|
82
89
|
|
83
90
|
if not chunks:
|
84
91
|
return
|
@@ -97,7 +104,10 @@ class ChromaRetriever:
|
|
97
104
|
documents=chunk_texts,
|
98
105
|
metadatas=cast(Any, metadatas),
|
99
106
|
)
|
100
|
-
print(
|
107
|
+
PrettyOutput.print(
|
108
|
+
f"成功将 {len(chunks)} 个块添加到 ChromaDB 集合中。",
|
109
|
+
OutputType.SUCCESS,
|
110
|
+
)
|
101
111
|
|
102
112
|
# 更新并保存BM25索引
|
103
113
|
tokenized_chunks = [doc.split() for doc in chunk_texts]
|
@@ -10,6 +10,7 @@ from jarvis.jarvis_platform.registry import PlatformRegistry
|
|
10
10
|
from jarvis.jarvis_utils.config import get_shell_name, set_config
|
11
11
|
from jarvis.jarvis_utils.input import get_multiline_input
|
12
12
|
from jarvis.jarvis_utils.utils import init_env
|
13
|
+
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
13
14
|
|
14
15
|
app = typer.Typer(
|
15
16
|
help="将自然语言要求转换为shell命令",
|
@@ -23,7 +24,7 @@ Example:
|
|
23
24
|
|
24
25
|
def execute_command(command: str, should_run: bool) -> None:
|
25
26
|
"""Print command without execution"""
|
26
|
-
print(command)
|
27
|
+
PrettyOutput.print(command, OutputType.CODE, lang="bash")
|
27
28
|
if should_run:
|
28
29
|
os.system(command)
|
29
30
|
|
@@ -58,38 +59,95 @@ def _get_markers() -> Tuple[str, str]:
|
|
58
59
|
)
|
59
60
|
|
60
61
|
|
62
|
+
def _check_bash_shell() -> bool:
|
63
|
+
"""Check if current shell is bash
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
bool: True if bash shell, False otherwise
|
67
|
+
"""
|
68
|
+
return get_shell_name() == "bash"
|
69
|
+
|
70
|
+
|
71
|
+
def _get_bash_config_file() -> str:
|
72
|
+
"""Get bash config file path
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
str: Path to bash config file (~/.bashrc)
|
76
|
+
"""
|
77
|
+
return os.path.expanduser("~/.bashrc")
|
78
|
+
|
79
|
+
|
80
|
+
def _get_bash_markers() -> Tuple[str, str]:
|
81
|
+
"""Get start and end markers for JSS completion in bash
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
Tuple[str, str]: (start_marker, end_marker)
|
85
|
+
"""
|
86
|
+
return (
|
87
|
+
"# ===== JARVIS JSS BASH COMPLETION START =====",
|
88
|
+
"# ===== JARVIS JSS BASH COMPLETION END =====",
|
89
|
+
)
|
90
|
+
|
91
|
+
|
92
|
+
def _check_zsh_shell() -> bool:
|
93
|
+
"""Check if current shell is zsh
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
bool: True if zsh shell, False otherwise
|
97
|
+
"""
|
98
|
+
return get_shell_name() == "zsh"
|
99
|
+
|
100
|
+
|
101
|
+
def _get_zsh_config_file() -> str:
|
102
|
+
"""Get zsh config file path
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
str: Path to zsh config file (~/.zshrc)
|
106
|
+
"""
|
107
|
+
return os.path.expanduser("~/.zshrc")
|
108
|
+
|
109
|
+
|
110
|
+
def _get_zsh_markers() -> Tuple[str, str]:
|
111
|
+
"""Get start and end markers for JSS completion in zsh
|
112
|
+
|
113
|
+
Returns:
|
114
|
+
Tuple[str, str]: (start_marker, end_marker)
|
115
|
+
"""
|
116
|
+
return (
|
117
|
+
"# ===== JARVIS JSS ZSH COMPLETION START =====",
|
118
|
+
"# ===== JARVIS JSS ZSH COMPLETION END =====",
|
119
|
+
)
|
120
|
+
|
121
|
+
|
61
122
|
@app.command("install")
|
62
123
|
def install_jss_completion(
|
63
|
-
shell: str = typer.Option("fish", help="指定shell类型(
|
124
|
+
shell: str = typer.Option("fish", help="指定shell类型(支持fish, bash, zsh)"),
|
64
125
|
) -> None:
|
65
|
-
"""
|
66
|
-
if shell
|
67
|
-
print(f"错误: 不支持的shell类型: {shell}, 仅支持fish")
|
126
|
+
"""为指定的shell安装'命令未找到'处理器,实现自然语言命令建议"""
|
127
|
+
if shell not in ("fish", "bash", "zsh"):
|
128
|
+
PrettyOutput.print(f"错误: 不支持的shell类型: {shell}, 仅支持fish, bash, zsh", OutputType.ERROR)
|
68
129
|
raise typer.Exit(code=1)
|
69
130
|
|
70
|
-
if
|
71
|
-
|
72
|
-
|
131
|
+
if shell == "fish":
|
132
|
+
config_file = _get_config_file()
|
133
|
+
start_marker, end_marker = _get_markers()
|
73
134
|
|
74
|
-
|
75
|
-
|
135
|
+
if not os.path.exists(config_file):
|
136
|
+
PrettyOutput.print("未找到 config.fish 文件,将创建新文件", OutputType.INFO)
|
137
|
+
os.makedirs(os.path.dirname(config_file), exist_ok=True)
|
138
|
+
with open(config_file, "w") as f:
|
139
|
+
f.write("")
|
76
140
|
|
77
|
-
|
78
|
-
|
79
|
-
os.makedirs(os.path.dirname(config_file), exist_ok=True)
|
80
|
-
with open(config_file, "w") as f:
|
81
|
-
f.write("")
|
141
|
+
with open(config_file, "r") as f:
|
142
|
+
content = f.read()
|
82
143
|
|
83
|
-
|
84
|
-
|
144
|
+
if start_marker in content:
|
145
|
+
PrettyOutput.print("JSS fish completion 已安装,请执行: source ~/.config/fish/config.fish", OutputType.SUCCESS)
|
146
|
+
return
|
85
147
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
with open(config_file, "a") as f:
|
91
|
-
f.write(
|
92
|
-
f"""
|
148
|
+
with open(config_file, "a") as f:
|
149
|
+
f.write(
|
150
|
+
f"""
|
93
151
|
{start_marker}
|
94
152
|
function fish_command_not_found
|
95
153
|
if test (string length "$argv") -lt 10
|
@@ -103,43 +161,245 @@ function __fish_command_not_found_handler --on-event fish_command_not_found
|
|
103
161
|
end
|
104
162
|
{end_marker}
|
105
163
|
"""
|
106
|
-
|
107
|
-
|
164
|
+
)
|
165
|
+
PrettyOutput.print("JSS fish completion 已安装,请执行: source ~/.config/fish/config.fish", OutputType.SUCCESS)
|
166
|
+
elif shell == "bash":
|
167
|
+
config_file = _get_bash_config_file()
|
168
|
+
start_marker, end_marker = _get_bash_markers()
|
169
|
+
|
170
|
+
if not os.path.exists(config_file):
|
171
|
+
PrettyOutput.print("未找到 ~/.bashrc 文件,将创建新文件", OutputType.INFO)
|
172
|
+
os.makedirs(os.path.dirname(config_file), exist_ok=True)
|
173
|
+
with open(config_file, "w") as f:
|
174
|
+
f.write("")
|
175
|
+
|
176
|
+
with open(config_file, "r") as f:
|
177
|
+
content = f.read()
|
178
|
+
|
179
|
+
if start_marker in content:
|
180
|
+
PrettyOutput.print("JSS bash completion 已安装,请执行: source ~/.bashrc", OutputType.SUCCESS)
|
181
|
+
return
|
182
|
+
else:
|
183
|
+
with open(config_file, "a") as f:
|
184
|
+
f.write(
|
185
|
+
f"""
|
186
|
+
{start_marker}
|
187
|
+
# Bash 'command not found' handler for JSS
|
188
|
+
# 行为:
|
189
|
+
# - 生成可编辑的建议命令,用户可直接编辑后回车执行
|
190
|
+
# - 非交互模式下仅打印建议
|
191
|
+
command_not_found_handle() {{
|
192
|
+
local cmd="$1"
|
193
|
+
shift || true
|
194
|
+
local text="$cmd $*"
|
195
|
+
|
196
|
+
# 与 fish 行为保持一致:对过短输入不处理
|
197
|
+
if [ ${{#text}} -lt 10 ]; then
|
198
|
+
return 127
|
199
|
+
fi
|
200
|
+
|
201
|
+
local suggestion edited
|
202
|
+
suggestion=$(jss request "$text")
|
203
|
+
if [ -n "$suggestion" ]; then
|
204
|
+
# 交互式:用 readline 预填命令,用户可直接回车执行或编辑
|
205
|
+
if [[ $- == *i* ]]; then
|
206
|
+
edited="$suggestion"
|
207
|
+
# -e 启用 readline;-i 预填默认值;无提示前缀,使体验更接近 fish 的“替换命令行”
|
208
|
+
read -e -i "$edited" edited
|
209
|
+
if [ -n "$edited" ]; then
|
210
|
+
eval "$edited"
|
211
|
+
return $?
|
212
|
+
fi
|
213
|
+
else
|
214
|
+
# 非交互:仅打印建议
|
215
|
+
printf '%s\n' "$suggestion"
|
216
|
+
fi
|
217
|
+
fi
|
218
|
+
return 127
|
219
|
+
}}
|
220
|
+
{end_marker}
|
221
|
+
"""
|
222
|
+
)
|
223
|
+
PrettyOutput.print("JSS bash completion 已安装,请执行: source ~/.bashrc", OutputType.SUCCESS)
|
224
|
+
elif shell == "zsh":
|
225
|
+
config_file = _get_zsh_config_file()
|
226
|
+
start_marker, end_marker = _get_zsh_markers()
|
227
|
+
|
228
|
+
if not os.path.exists(config_file):
|
229
|
+
PrettyOutput.print("未找到 ~/.zshrc 文件,将创建新文件", OutputType.INFO)
|
230
|
+
os.makedirs(os.path.dirname(config_file), exist_ok=True)
|
231
|
+
with open(config_file, "w") as f:
|
232
|
+
f.write("")
|
233
|
+
|
234
|
+
with open(config_file, "r") as f:
|
235
|
+
content = f.read()
|
236
|
+
|
237
|
+
if start_marker in content:
|
238
|
+
PrettyOutput.print("JSS zsh completion 已安装,请执行: source ~/.zshrc", OutputType.SUCCESS)
|
239
|
+
return
|
240
|
+
|
241
|
+
with open(config_file, "a") as f:
|
242
|
+
f.write(
|
243
|
+
f"""
|
244
|
+
{start_marker}
|
245
|
+
# Zsh 'command not found' handler for JSS
|
246
|
+
# 行为:
|
247
|
+
# - 生成可编辑的建议命令,用户可直接编辑后回车执行
|
248
|
+
# - 非交互模式下仅打印建议
|
249
|
+
command_not_found_handler() {{
|
250
|
+
local cmd="$1"
|
251
|
+
shift || true
|
252
|
+
local text="$cmd $*"
|
253
|
+
|
254
|
+
# 与 fish 行为保持一致:对过短输入不处理
|
255
|
+
if [ ${{#text}} -lt 10 ]; then
|
256
|
+
return 127
|
257
|
+
fi
|
258
|
+
|
259
|
+
local suggestion edited
|
260
|
+
suggestion=$(jss request "$text")
|
261
|
+
if [ -n "$suggestion" ]; then
|
262
|
+
if [[ -o interactive ]]; then
|
263
|
+
local editor="${{VISUAL:-${{EDITOR:-vi}}}}"
|
264
|
+
local tmpfile edited
|
265
|
+
tmpfile="$(mktemp -t jss-edit-XXXXXX)"
|
266
|
+
printf '%s\n' "$suggestion" > "$tmpfile"
|
267
|
+
"$editor" "$tmpfile"
|
268
|
+
edited="$(sed -n '/./{{p;q;}}' "$tmpfile" | tr -d '\r')"
|
269
|
+
rm -f "$tmpfile"
|
270
|
+
if [ -z "$edited" ]; then
|
271
|
+
edited="$suggestion"
|
272
|
+
fi
|
273
|
+
eval "$edited"
|
274
|
+
return $?
|
275
|
+
else
|
276
|
+
# 非交互:仅打印建议
|
277
|
+
print -r -- "$suggestion"
|
278
|
+
fi
|
279
|
+
fi
|
280
|
+
return 127
|
281
|
+
}}
|
282
|
+
{end_marker}
|
283
|
+
"""
|
284
|
+
)
|
285
|
+
PrettyOutput.print("JSS zsh completion 已安装,请执行: source ~/.zshrc", OutputType.SUCCESS)
|
286
|
+
return
|
287
|
+
|
288
|
+
with open(config_file, "a") as f:
|
289
|
+
f.write(
|
290
|
+
f"""
|
291
|
+
{start_marker}
|
292
|
+
# Bash 'command not found' handler for JSS
|
293
|
+
# 行为:
|
294
|
+
# - 生成可编辑的建议命令,用户可直接编辑后回车执行
|
295
|
+
# - 非交互模式下仅打印建议
|
296
|
+
command_not_found_handle() {{
|
297
|
+
local cmd="$1"
|
298
|
+
shift || true
|
299
|
+
local text="$cmd $*"
|
300
|
+
|
301
|
+
# 与 fish 行为保持一致:对过短输入不处理
|
302
|
+
if [ ${{#text}} -lt 10 ]; then
|
303
|
+
return 127
|
304
|
+
fi
|
305
|
+
|
306
|
+
local suggestion edited
|
307
|
+
suggestion=$(jss request "$text")
|
308
|
+
if [ -n "$suggestion" ]; then
|
309
|
+
# 交互式:用 readline 预填命令,用户可直接回车执行或编辑
|
310
|
+
if [[ $- == *i* ]]; then
|
311
|
+
edited="$suggestion"
|
312
|
+
# -e 启用 readline;-i 预填默认值;无提示前缀,使体验更接近 fish 的“替换命令行”
|
313
|
+
read -e -i "$edited" edited
|
314
|
+
if [ -n "$edited" ]; then
|
315
|
+
eval "$edited"
|
316
|
+
return $?
|
317
|
+
fi
|
318
|
+
else
|
319
|
+
# 非交互:仅打印建议
|
320
|
+
printf '%s\n' "$suggestion"
|
321
|
+
fi
|
322
|
+
fi
|
323
|
+
return 127
|
324
|
+
}}
|
325
|
+
{end_marker}
|
326
|
+
"""
|
327
|
+
)
|
328
|
+
PrettyOutput.print("JSS bash completion 已安装,请执行: source ~/.bashrc", OutputType.SUCCESS)
|
108
329
|
|
109
330
|
|
110
331
|
@app.command("uninstall")
|
111
332
|
def uninstall_jss_completion(
|
112
|
-
shell: str = typer.Option("fish", help="指定shell类型(
|
333
|
+
shell: str = typer.Option("fish", help="指定shell类型(支持fish, bash, zsh)"),
|
113
334
|
) -> None:
|
114
|
-
"""卸载JSS
|
115
|
-
if shell
|
116
|
-
print(f"错误: 不支持的shell类型: {shell}, 仅支持fish")
|
335
|
+
"""卸载JSS shell'命令未找到'处理器"""
|
336
|
+
if shell not in ("fish", "bash", "zsh"):
|
337
|
+
PrettyOutput.print(f"错误: 不支持的shell类型: {shell}, 仅支持fish, bash, zsh", OutputType.ERROR)
|
117
338
|
raise typer.Exit(code=1)
|
118
339
|
|
119
|
-
if
|
120
|
-
|
121
|
-
|
340
|
+
if shell == "fish":
|
341
|
+
config_file = _get_config_file()
|
342
|
+
start_marker, end_marker = _get_markers()
|
122
343
|
|
123
|
-
|
124
|
-
|
344
|
+
if not os.path.exists(config_file):
|
345
|
+
PrettyOutput.print("未找到 JSS fish completion 配置,无需卸载", OutputType.INFO)
|
346
|
+
return
|
125
347
|
|
126
|
-
|
127
|
-
|
128
|
-
return
|
348
|
+
with open(config_file, "r") as f:
|
349
|
+
content = f.read()
|
129
350
|
|
130
|
-
|
131
|
-
|
351
|
+
if start_marker not in content:
|
352
|
+
PrettyOutput.print("未找到 JSS fish completion 配置,无需卸载", OutputType.INFO)
|
353
|
+
return
|
132
354
|
|
133
|
-
|
134
|
-
|
135
|
-
|
355
|
+
new_content = content.split(start_marker)[0] + content.split(end_marker)[-1]
|
356
|
+
|
357
|
+
with open(config_file, "w") as f:
|
358
|
+
f.write(new_content)
|
359
|
+
|
360
|
+
PrettyOutput.print("JSS fish completion 已卸载,请执行: source ~/.config/fish/config.fish", OutputType.SUCCESS)
|
361
|
+
elif shell == "bash":
|
362
|
+
config_file = _get_bash_config_file()
|
363
|
+
start_marker, end_marker = _get_bash_markers()
|
364
|
+
|
365
|
+
if not os.path.exists(config_file):
|
366
|
+
PrettyOutput.print("未找到 JSS bash completion 配置,无需卸载", OutputType.INFO)
|
367
|
+
return
|
368
|
+
|
369
|
+
with open(config_file, "r") as f:
|
370
|
+
content = f.read()
|
371
|
+
|
372
|
+
if start_marker not in content:
|
373
|
+
PrettyOutput.print("未找到 JSS bash completion 配置,无需卸载", OutputType.INFO)
|
374
|
+
return
|
136
375
|
|
137
|
-
|
376
|
+
new_content = content.split(start_marker)[0] + content.split(end_marker)[-1]
|
138
377
|
|
139
|
-
|
140
|
-
|
378
|
+
with open(config_file, "w") as f:
|
379
|
+
f.write(new_content)
|
380
|
+
|
381
|
+
PrettyOutput.print("JSS bash completion 已卸载,请执行: source ~/.bashrc", OutputType.SUCCESS)
|
382
|
+
elif shell == "zsh":
|
383
|
+
config_file = _get_zsh_config_file()
|
384
|
+
start_marker, end_marker = _get_zsh_markers()
|
385
|
+
|
386
|
+
if not os.path.exists(config_file):
|
387
|
+
PrettyOutput.print("未找到 JSS zsh completion 配置,无需卸载", OutputType.INFO)
|
388
|
+
return
|
389
|
+
|
390
|
+
with open(config_file, "r") as f:
|
391
|
+
content = f.read()
|
392
|
+
|
393
|
+
if start_marker not in content:
|
394
|
+
PrettyOutput.print("未找到 JSS zsh completion 配置,无需卸载", OutputType.INFO)
|
395
|
+
return
|
396
|
+
|
397
|
+
new_content = content.split(start_marker)[0] + content.split(end_marker)[-1]
|
398
|
+
|
399
|
+
with open(config_file, "w") as f:
|
400
|
+
f.write(new_content)
|
141
401
|
|
142
|
-
|
402
|
+
PrettyOutput.print("JSS zsh completion 已卸载,请执行: source ~/.zshrc", OutputType.SUCCESS)
|
143
403
|
|
144
404
|
|
145
405
|
def process_request(request: str) -> Optional[str]:
|