ima-python-sdk 0.1.0__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.
- ima_python_sdk-0.1.0.dist-info/METADATA +93 -0
- ima_python_sdk-0.1.0.dist-info/RECORD +15 -0
- ima_python_sdk-0.1.0.dist-info/WHEEL +5 -0
- ima_python_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- ima_python_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- ima_python_sdk-0.1.0.dist-info/top_level.txt +1 -0
- ima_sdk/__init__.py +90 -0
- ima_sdk/cli.py +447 -0
- ima_sdk/client.py +268 -0
- ima_sdk/cos_uploader.py +158 -0
- ima_sdk/file_checker.py +226 -0
- ima_sdk/knowledge_base.py +591 -0
- ima_sdk/logger.py +89 -0
- ima_sdk/notes.py +343 -0
- ima_sdk/types.py +424 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ima-python-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 非官方的 IMA OpenAPI Python SDK,提供知识库和笔记管理功能
|
|
5
|
+
Author: elxy
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/elxy/ima-python-sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/elxy/ima-python-sdk
|
|
9
|
+
Project-URL: Issues, https://github.com/elxy/ima-python-sdk/issues
|
|
10
|
+
Keywords: ima,knowledge-base,notes,sdk
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# ima-python-sdk
|
|
27
|
+
|
|
28
|
+
非官方的 IMA OpenAPI Python SDK,提供知识库和笔记管理功能。
|
|
29
|
+
|
|
30
|
+
> 本项目参考 [ima-skills](https://clawhub.ai/iampennyli/ima-skills) 中的 API 文档和脚本实现,与 IMA 官方无关。
|
|
31
|
+
|
|
32
|
+
## 安装
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install ima-python-sdk
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 配置
|
|
39
|
+
|
|
40
|
+
SDK 需要 `client_id` 和 `api_key` 两个凭证,按以下优先级自动发现:
|
|
41
|
+
|
|
42
|
+
1. **构造参数** — `ImaClient(client_id="...", api_key="...")`
|
|
43
|
+
2. **环境变量** — `IMA_OPENAPI_CLIENTID` / `IMA_OPENAPI_APIKEY`
|
|
44
|
+
3. **配置文件** — `~/.config/ima/client_id` / `~/.config/ima/api_key`
|
|
45
|
+
|
|
46
|
+
## CLI 使用
|
|
47
|
+
|
|
48
|
+
`--kb_id` 可通过环境变量 `IMA_KB_ID` 设置,避免每次手动指定。
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# 知识库
|
|
52
|
+
ima-sdk kb addable
|
|
53
|
+
ima-sdk kb upload report.pdf --kb_id xxx # 上传到根目录
|
|
54
|
+
ima-sdk kb upload report.pdf 论文/机器学习 --kb_id xxx # 上传到指定目录
|
|
55
|
+
ima-sdk kb import-url https://example.com --kb_id xxx
|
|
56
|
+
|
|
57
|
+
# 笔记
|
|
58
|
+
ima-sdk note search "会议记录"
|
|
59
|
+
ima-sdk note create "# 标题"
|
|
60
|
+
ima-sdk note read <doc_id> --raw
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## SDK 使用
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from ima_sdk import ImaClient, KnowledgeBaseManager, NotesManager
|
|
67
|
+
|
|
68
|
+
client = ImaClient() # 自动读取环境变量或配置文件
|
|
69
|
+
kb = KnowledgeBaseManager(client)
|
|
70
|
+
notes = NotesManager(client)
|
|
71
|
+
|
|
72
|
+
# 知识库操作
|
|
73
|
+
kb.upload_file("/path/to/report.pdf", knowledge_base_id="xxx")
|
|
74
|
+
kb.import_urls(["https://example.com"], knowledge_base_id="xxx")
|
|
75
|
+
results = kb.search_knowledge("关键词", knowledge_base_id="xxx")
|
|
76
|
+
|
|
77
|
+
# 笔记操作
|
|
78
|
+
doc_id = notes.create("# 标题\n\n正文内容")
|
|
79
|
+
docs, total, is_end = notes.search("会议记录")
|
|
80
|
+
content = notes.get_content(doc_id)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 特性
|
|
84
|
+
|
|
85
|
+
- **零第三方依赖** — 仅使用 Python 标准库(urllib, hmac, hashlib, dataclasses)
|
|
86
|
+
- **凭证自动发现** — 构造参数 > 环境变量 > 配置文件,三级优先
|
|
87
|
+
- **自动翻页** — 列表接口支持 `fetch_all=True`
|
|
88
|
+
- **完整上传流程** — `upload_file()` 一个方法完成预检、重名检查、创建媒体、COS 上传、添加知识
|
|
89
|
+
- **UTF-8 安全** — 笔记写入前自动校验编码
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ima_python_sdk-0.1.0.dist-info/licenses/LICENSE,sha256=Mi2ivYnXBdi7QDrfLPnxgyujUcGuHNEGBV-1b4kYVhc,1061
|
|
2
|
+
ima_sdk/__init__.py,sha256=oJTlVRs08wdQkye-IQzZaJesNA1Uf3XOm7Fp6RiJu-k,1956
|
|
3
|
+
ima_sdk/cli.py,sha256=m3yOfH8bFf9ZQJwZ1772fmHFXHVu1_Ie1pCUgpynDfs,15708
|
|
4
|
+
ima_sdk/client.py,sha256=Tx21aJSWDRkzpOkxHPKTFS_L1MF-511k8MYHH-Hnnu4,9717
|
|
5
|
+
ima_sdk/cos_uploader.py,sha256=BEINVD4N8PbE_nHe0XocLtQFPLt-46T-qm1l9RJD0UI,4813
|
|
6
|
+
ima_sdk/file_checker.py,sha256=LWO-yeOIFFGXvvqqvF2CWDyzp9n9nsxlHtK5KDsw0yI,8266
|
|
7
|
+
ima_sdk/knowledge_base.py,sha256=8n-cr-J2lQaDm8Lqmww_CrUsYh9Imw_vLBbOsSho7ek,20965
|
|
8
|
+
ima_sdk/logger.py,sha256=FEYNSM96G-b2wRJkGHcnoOArUD22Fm4wrstfuYYrIbY,2395
|
|
9
|
+
ima_sdk/notes.py,sha256=GoRyn1qXs3AL9zu6oSRCuYXepVxNtVfOCG7hH8wfNfM,11323
|
|
10
|
+
ima_sdk/types.py,sha256=oHYCsEo1YK_6fTxZC4lpPC4U_XZh6W5QuSTVy_XK3is,11346
|
|
11
|
+
ima_python_sdk-0.1.0.dist-info/METADATA,sha256=GwP77JyDpPlRwTCda0e7Rk8RopSwoczXYfLIESNa8Aw,3117
|
|
12
|
+
ima_python_sdk-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
13
|
+
ima_python_sdk-0.1.0.dist-info/entry_points.txt,sha256=dugqsz4PVgamfgxXz9TdDoZEfgQMkNKAkNRKRCgHy6s,45
|
|
14
|
+
ima_python_sdk-0.1.0.dist-info/top_level.txt,sha256=sZundEb6Z_upgSYPSF_XyJLnHM9k45r9lgzdfLfoPLw,8
|
|
15
|
+
ima_python_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 elxy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ima_sdk
|
ima_sdk/__init__.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""IMA Python SDK — 知识库和笔记操作。
|
|
2
|
+
|
|
3
|
+
用法::
|
|
4
|
+
|
|
5
|
+
from ima_sdk import ImaClient, KnowledgeBaseManager, NotesManager
|
|
6
|
+
|
|
7
|
+
client = ImaClient()
|
|
8
|
+
kb = KnowledgeBaseManager(client)
|
|
9
|
+
notes = NotesManager(client)
|
|
10
|
+
|
|
11
|
+
# 搜索知识库
|
|
12
|
+
results = kb.search_knowledge_base("我的资料")
|
|
13
|
+
|
|
14
|
+
# 上传文件到知识库
|
|
15
|
+
kb.upload_file("/path/to/report.pdf", knowledge_base_id="xxx")
|
|
16
|
+
|
|
17
|
+
# 创建笔记
|
|
18
|
+
doc_id = notes.create("# 标题\\n\\n正文内容")
|
|
19
|
+
|
|
20
|
+
# 搜索笔记
|
|
21
|
+
docs, total, is_end = notes.search("会议记录")
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
__version__ = "0.1.0"
|
|
25
|
+
|
|
26
|
+
from .client import ImaApiError, ImaClient
|
|
27
|
+
from .cos_uploader import cos_upload
|
|
28
|
+
from .file_checker import check_file
|
|
29
|
+
from .knowledge_base import KnowledgeBaseManager
|
|
30
|
+
from .notes import NotesManager
|
|
31
|
+
from .types import (
|
|
32
|
+
AddableKnowledgeBaseInfo,
|
|
33
|
+
CheckRepeatedNameResult,
|
|
34
|
+
ContentFormat,
|
|
35
|
+
CosCredential,
|
|
36
|
+
CreateMediaResult,
|
|
37
|
+
DocBasic,
|
|
38
|
+
FileCheckResult,
|
|
39
|
+
FileInfo,
|
|
40
|
+
FolderType,
|
|
41
|
+
ImportURLResult,
|
|
42
|
+
KBFolderInfo,
|
|
43
|
+
KnowledgeBaseInfo,
|
|
44
|
+
KnowledgeInfo,
|
|
45
|
+
KnowledgeListResult,
|
|
46
|
+
MediaType,
|
|
47
|
+
NoteBookInfo,
|
|
48
|
+
NoteFolderItem,
|
|
49
|
+
SearchedDoc,
|
|
50
|
+
SearchedKnowledgeBaseInfo,
|
|
51
|
+
SearchedKnowledgeInfo,
|
|
52
|
+
SearchType,
|
|
53
|
+
SortType,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
# 核心
|
|
58
|
+
"ImaClient",
|
|
59
|
+
"ImaApiError",
|
|
60
|
+
# 管理器
|
|
61
|
+
"KnowledgeBaseManager",
|
|
62
|
+
"NotesManager",
|
|
63
|
+
# 工具函数
|
|
64
|
+
"check_file",
|
|
65
|
+
"cos_upload",
|
|
66
|
+
# 类型
|
|
67
|
+
"KnowledgeBaseInfo",
|
|
68
|
+
"KnowledgeInfo",
|
|
69
|
+
"KBFolderInfo",
|
|
70
|
+
"AddableKnowledgeBaseInfo",
|
|
71
|
+
"SearchedKnowledgeBaseInfo",
|
|
72
|
+
"SearchedKnowledgeInfo",
|
|
73
|
+
"ImportURLResult",
|
|
74
|
+
"CosCredential",
|
|
75
|
+
"FileInfo",
|
|
76
|
+
"CreateMediaResult",
|
|
77
|
+
"CheckRepeatedNameResult",
|
|
78
|
+
"KnowledgeListResult",
|
|
79
|
+
"FileCheckResult",
|
|
80
|
+
"DocBasic",
|
|
81
|
+
"NoteFolderItem",
|
|
82
|
+
"NoteBookInfo",
|
|
83
|
+
"SearchedDoc",
|
|
84
|
+
# 枚举
|
|
85
|
+
"MediaType",
|
|
86
|
+
"ContentFormat",
|
|
87
|
+
"SearchType",
|
|
88
|
+
"SortType",
|
|
89
|
+
"FolderType",
|
|
90
|
+
]
|
ima_sdk/cli.py
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""IMA SDK 命令行工具。
|
|
2
|
+
|
|
3
|
+
使用 argparse 子命令模式提供知识库和笔记的命令行操作。
|
|
4
|
+
|
|
5
|
+
用法::
|
|
6
|
+
|
|
7
|
+
# 知识库操作
|
|
8
|
+
ima-sdk kb search-base <query>
|
|
9
|
+
ima-sdk kb info <kb_id>
|
|
10
|
+
ima-sdk kb list <kb_id>
|
|
11
|
+
ima-sdk kb search <query> --kb_id <kb_id>
|
|
12
|
+
ima-sdk kb upload <file_path> [dest_dir] --kb_id <kb_id>
|
|
13
|
+
ima-sdk kb upload <file_path> [dest_dir] # kb_id 从 IMA_KB_ID 环境变量读取
|
|
14
|
+
ima-sdk kb import-url <url> --kb_id <kb_id>
|
|
15
|
+
ima-sdk kb addable
|
|
16
|
+
|
|
17
|
+
# 笔记操作
|
|
18
|
+
ima-sdk note search <query>
|
|
19
|
+
ima-sdk note folders
|
|
20
|
+
ima-sdk note list [--folder_id <id>]
|
|
21
|
+
ima-sdk note create <content_or_file>
|
|
22
|
+
ima-sdk note append <doc_id> <content_or_file>
|
|
23
|
+
ima-sdk note read <doc_id>
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
import sys
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _json_output(obj: Any) -> str:
|
|
36
|
+
"""将对象序列化为可读的 JSON 字符串。"""
|
|
37
|
+
if hasattr(obj, "__dataclass_fields__"):
|
|
38
|
+
from dataclasses import asdict
|
|
39
|
+
|
|
40
|
+
return json.dumps(asdict(obj), ensure_ascii=False, indent=2)
|
|
41
|
+
if isinstance(obj, list):
|
|
42
|
+
from dataclasses import asdict
|
|
43
|
+
|
|
44
|
+
items = []
|
|
45
|
+
for item in obj:
|
|
46
|
+
if hasattr(item, "__dataclass_fields__"):
|
|
47
|
+
items.append(asdict(item))
|
|
48
|
+
elif isinstance(item, dict):
|
|
49
|
+
items.append(item)
|
|
50
|
+
else:
|
|
51
|
+
items.append(str(item))
|
|
52
|
+
return json.dumps(items, ensure_ascii=False, indent=2)
|
|
53
|
+
if isinstance(obj, dict):
|
|
54
|
+
from dataclasses import asdict
|
|
55
|
+
|
|
56
|
+
result = {}
|
|
57
|
+
for k, v in obj.items():
|
|
58
|
+
if hasattr(v, "__dataclass_fields__"):
|
|
59
|
+
result[k] = asdict(v)
|
|
60
|
+
else:
|
|
61
|
+
result[k] = v
|
|
62
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
63
|
+
return json.dumps(obj, ensure_ascii=False, indent=2)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _read_content(value: str) -> str:
|
|
67
|
+
"""读取内容:如果 value 是存在的文件路径则读取文件,否则作为直接内容。"""
|
|
68
|
+
if os.path.isfile(value):
|
|
69
|
+
with open(value, "r", encoding="utf-8") as f:
|
|
70
|
+
return f.read()
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _get_client(*, curl_mode: bool = False):
|
|
75
|
+
"""创建 ImaClient 实例。"""
|
|
76
|
+
from .client import ImaClient
|
|
77
|
+
|
|
78
|
+
return ImaClient(curl_mode=curl_mode)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _require_kb_id(args: argparse.Namespace) -> str:
|
|
82
|
+
"""校验 kb_id 存在,缺失时报错退出。"""
|
|
83
|
+
if not args.kb_id:
|
|
84
|
+
print(
|
|
85
|
+
"错误: 必须通过 --kb_id 或环境变量 IMA_KB_ID 指定知识库 ID",
|
|
86
|
+
file=sys.stderr,
|
|
87
|
+
)
|
|
88
|
+
sys.exit(1)
|
|
89
|
+
return args.kb_id
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ── 知识库子命令 ──────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _kb_search_base(args: argparse.Namespace) -> None:
|
|
96
|
+
from .knowledge_base import KnowledgeBaseManager
|
|
97
|
+
|
|
98
|
+
kb = KnowledgeBaseManager(_get_client(curl_mode=args.curl))
|
|
99
|
+
results = kb.search_knowledge_base(args.query, fetch_all=args.all)
|
|
100
|
+
print(_json_output(results))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _kb_info(args: argparse.Namespace) -> None:
|
|
104
|
+
from .knowledge_base import KnowledgeBaseManager
|
|
105
|
+
|
|
106
|
+
kb = KnowledgeBaseManager(_get_client(curl_mode=args.curl))
|
|
107
|
+
results = kb.get_knowledge_base(args.ids)
|
|
108
|
+
print(_json_output(results))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _kb_list(args: argparse.Namespace) -> None:
|
|
112
|
+
from .knowledge_base import KnowledgeBaseManager
|
|
113
|
+
|
|
114
|
+
_require_kb_id(args)
|
|
115
|
+
kb = KnowledgeBaseManager(_get_client(curl_mode=args.curl))
|
|
116
|
+
if args.all:
|
|
117
|
+
knowledge, folders = kb.get_all_knowledge(
|
|
118
|
+
args.kb_id, folder_id=args.folder_id
|
|
119
|
+
)
|
|
120
|
+
print(
|
|
121
|
+
_json_output({"knowledge": knowledge, "folders": folders})
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
result = kb.get_knowledge_list(
|
|
125
|
+
args.kb_id, folder_id=args.folder_id, limit=args.limit
|
|
126
|
+
)
|
|
127
|
+
print(_json_output(result))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _kb_search(args: argparse.Namespace) -> None:
|
|
131
|
+
from .knowledge_base import KnowledgeBaseManager
|
|
132
|
+
|
|
133
|
+
_require_kb_id(args)
|
|
134
|
+
kb = KnowledgeBaseManager(_get_client(curl_mode=args.curl))
|
|
135
|
+
results = kb.search_knowledge(
|
|
136
|
+
args.query, args.kb_id, fetch_all=args.all
|
|
137
|
+
)
|
|
138
|
+
print(_json_output(results))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _kb_upload(args: argparse.Namespace) -> None:
|
|
142
|
+
from .knowledge_base import KnowledgeBaseManager
|
|
143
|
+
|
|
144
|
+
_require_kb_id(args)
|
|
145
|
+
kb = KnowledgeBaseManager(_get_client(curl_mode=args.curl))
|
|
146
|
+
|
|
147
|
+
# dest_dir 和 --folder_id 互斥,dest_dir 需要逐级解析
|
|
148
|
+
folder_id = args.folder_id
|
|
149
|
+
if args.dest_dir:
|
|
150
|
+
if folder_id:
|
|
151
|
+
print("错误: --folder_id 和 dest_dir 不能同时指定", file=sys.stderr)
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
folder_id = kb.resolve_folder_path(args.kb_id, args.dest_dir)
|
|
154
|
+
|
|
155
|
+
if args.dry_run:
|
|
156
|
+
result = kb.dry_run_upload(
|
|
157
|
+
args.file_path,
|
|
158
|
+
args.kb_id,
|
|
159
|
+
folder_id=folder_id,
|
|
160
|
+
)
|
|
161
|
+
print(_json_output(result))
|
|
162
|
+
else:
|
|
163
|
+
media_id = kb.upload_file(
|
|
164
|
+
args.file_path,
|
|
165
|
+
args.kb_id,
|
|
166
|
+
folder_id=folder_id,
|
|
167
|
+
skip_duplicate_check=args.skip_dup,
|
|
168
|
+
)
|
|
169
|
+
print(json.dumps({"media_id": media_id}, ensure_ascii=False))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _kb_import_url(args: argparse.Namespace) -> None:
|
|
173
|
+
from .knowledge_base import KnowledgeBaseManager
|
|
174
|
+
|
|
175
|
+
_require_kb_id(args)
|
|
176
|
+
kb = KnowledgeBaseManager(_get_client(curl_mode=args.curl))
|
|
177
|
+
results = kb.import_urls(
|
|
178
|
+
args.urls, args.kb_id, folder_id=args.folder_id
|
|
179
|
+
)
|
|
180
|
+
print(_json_output(results))
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _kb_addable(args: argparse.Namespace) -> None:
|
|
184
|
+
from .knowledge_base import KnowledgeBaseManager
|
|
185
|
+
|
|
186
|
+
kb = KnowledgeBaseManager(_get_client(curl_mode=args.curl))
|
|
187
|
+
results = kb.get_addable_list(fetch_all=args.all)
|
|
188
|
+
print(_json_output(results))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ── 笔记子命令 ────────────────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _note_search(args: argparse.Namespace) -> None:
|
|
195
|
+
from .notes import NotesManager
|
|
196
|
+
|
|
197
|
+
notes = NotesManager(_get_client(curl_mode=args.curl))
|
|
198
|
+
if args.all:
|
|
199
|
+
results = notes.search_all(
|
|
200
|
+
args.query, search_type=args.search_type, sort_type=args.sort_type
|
|
201
|
+
)
|
|
202
|
+
print(_json_output(results))
|
|
203
|
+
else:
|
|
204
|
+
docs, total, is_end = notes.search(
|
|
205
|
+
args.query,
|
|
206
|
+
search_type=args.search_type,
|
|
207
|
+
sort_type=args.sort_type,
|
|
208
|
+
start=args.start,
|
|
209
|
+
end=args.end,
|
|
210
|
+
)
|
|
211
|
+
print(
|
|
212
|
+
_json_output(
|
|
213
|
+
{"docs": docs, "total_hit_num": total, "is_end": is_end}
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _note_folders(args: argparse.Namespace) -> None:
|
|
219
|
+
from .notes import NotesManager
|
|
220
|
+
|
|
221
|
+
notes = NotesManager(_get_client(curl_mode=args.curl))
|
|
222
|
+
if args.all:
|
|
223
|
+
results = notes.list_all_folders()
|
|
224
|
+
else:
|
|
225
|
+
results = notes.list_folders(limit=args.limit)
|
|
226
|
+
print(_json_output(results))
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _note_list(args: argparse.Namespace) -> None:
|
|
230
|
+
from .notes import NotesManager
|
|
231
|
+
|
|
232
|
+
notes = NotesManager(_get_client(curl_mode=args.curl))
|
|
233
|
+
if args.all:
|
|
234
|
+
results = notes.list_all_notes(folder_id=args.folder_id)
|
|
235
|
+
print(_json_output(results))
|
|
236
|
+
else:
|
|
237
|
+
note_list, is_end, next_cursor = notes.list_notes(
|
|
238
|
+
folder_id=args.folder_id, limit=args.limit
|
|
239
|
+
)
|
|
240
|
+
print(
|
|
241
|
+
_json_output(
|
|
242
|
+
{
|
|
243
|
+
"notes": note_list,
|
|
244
|
+
"is_end": is_end,
|
|
245
|
+
"next_cursor": next_cursor,
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _note_create(args: argparse.Namespace) -> None:
|
|
252
|
+
from .notes import NotesManager
|
|
253
|
+
|
|
254
|
+
notes = NotesManager(_get_client(curl_mode=args.curl))
|
|
255
|
+
content = _read_content(args.content)
|
|
256
|
+
doc_id = notes.create(content, folder_id=args.folder_id)
|
|
257
|
+
print(json.dumps({"doc_id": doc_id}, ensure_ascii=False))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _note_append(args: argparse.Namespace) -> None:
|
|
261
|
+
from .notes import NotesManager
|
|
262
|
+
|
|
263
|
+
notes = NotesManager(_get_client(curl_mode=args.curl))
|
|
264
|
+
content = _read_content(args.content)
|
|
265
|
+
doc_id = notes.append(args.doc_id, content)
|
|
266
|
+
print(json.dumps({"doc_id": doc_id}, ensure_ascii=False))
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _note_read(args: argparse.Namespace) -> None:
|
|
270
|
+
from .notes import NotesManager
|
|
271
|
+
|
|
272
|
+
notes = NotesManager(_get_client(curl_mode=args.curl))
|
|
273
|
+
content = notes.get_content(args.doc_id, target_format=args.format)
|
|
274
|
+
if args.raw:
|
|
275
|
+
print(content)
|
|
276
|
+
else:
|
|
277
|
+
print(json.dumps({"content": content}, ensure_ascii=False, indent=2))
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# ── 主解析器 ──────────────────────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
284
|
+
"""构建命令行解析器。"""
|
|
285
|
+
parser = argparse.ArgumentParser(
|
|
286
|
+
prog="ima-sdk",
|
|
287
|
+
description="IMA 知识库和笔记命令行工具",
|
|
288
|
+
)
|
|
289
|
+
parser.add_argument(
|
|
290
|
+
"-v", "--verbose",
|
|
291
|
+
action="store_true",
|
|
292
|
+
help="启用详细日志输出(输出到 stderr)",
|
|
293
|
+
)
|
|
294
|
+
parser.add_argument(
|
|
295
|
+
"--curl",
|
|
296
|
+
action="store_true",
|
|
297
|
+
help="输出等价的 curl 命令到 stderr,方便调试",
|
|
298
|
+
)
|
|
299
|
+
subparsers = parser.add_subparsers(dest="module", help="功能模块")
|
|
300
|
+
|
|
301
|
+
# ── kb 模块 ───────────────────────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
kb_parser = subparsers.add_parser("kb", help="知识库操作")
|
|
304
|
+
kb_sub = kb_parser.add_subparsers(dest="command", help="知识库命令")
|
|
305
|
+
|
|
306
|
+
# kb search-base
|
|
307
|
+
p = kb_sub.add_parser("search-base", help="搜索知识库列表")
|
|
308
|
+
p.add_argument("query", help="搜索关键词")
|
|
309
|
+
p.add_argument("--all", action="store_true", help="获取全部结果(自动翻页)")
|
|
310
|
+
p.set_defaults(func=_kb_search_base)
|
|
311
|
+
|
|
312
|
+
# kb info
|
|
313
|
+
p = kb_sub.add_parser("info", help="获取知识库信息")
|
|
314
|
+
p.add_argument("ids", nargs="+", help="知识库 ID(支持多个)")
|
|
315
|
+
p.set_defaults(func=_kb_info)
|
|
316
|
+
|
|
317
|
+
# kb list
|
|
318
|
+
p = kb_sub.add_parser("list", help="浏览知识库内容")
|
|
319
|
+
p.add_argument("kb_id", nargs="?", default=os.environ.get("IMA_KB_ID"), help="知识库 ID(默认读取环境变量 IMA_KB_ID)")
|
|
320
|
+
p.add_argument("--folder_id", help="文件夹 ID")
|
|
321
|
+
p.add_argument("--limit", type=int, default=50, help="每页数量(默认 50)")
|
|
322
|
+
p.add_argument("--all", action="store_true", help="获取全部(自动翻页)")
|
|
323
|
+
p.set_defaults(func=_kb_list)
|
|
324
|
+
|
|
325
|
+
# kb search
|
|
326
|
+
p = kb_sub.add_parser("search", help="搜索知识库内容")
|
|
327
|
+
p.add_argument("query", help="搜索关键词")
|
|
328
|
+
p.add_argument("--kb_id", default=os.environ.get("IMA_KB_ID"), help="知识库 ID(默认读取环境变量 IMA_KB_ID)")
|
|
329
|
+
p.add_argument("--all", action="store_true", help="获取全部结果")
|
|
330
|
+
p.set_defaults(func=_kb_search)
|
|
331
|
+
|
|
332
|
+
# kb upload
|
|
333
|
+
p = kb_sub.add_parser("upload", help="上传文件到知识库")
|
|
334
|
+
p.add_argument("file_path", help="文件路径")
|
|
335
|
+
p.add_argument("dest_dir", nargs="?", default="", help="目标目录路径,如 '论文/机器学习'(默认根目录)")
|
|
336
|
+
p.add_argument("--kb_id", default=os.environ.get("IMA_KB_ID"), help="知识库 ID(默认读取环境变量 IMA_KB_ID)")
|
|
337
|
+
p.add_argument("--folder_id", help="文件夹 ID(与 dest_dir 互斥)")
|
|
338
|
+
p.add_argument("--skip_dup", action="store_true", help="跳过重名检查")
|
|
339
|
+
p.add_argument("--dry_run", action="store_true", help="只做预检和重名检查,不实际上传")
|
|
340
|
+
p.set_defaults(func=_kb_upload)
|
|
341
|
+
|
|
342
|
+
# kb import-url
|
|
343
|
+
p = kb_sub.add_parser("import-url", help="导入网页到知识库")
|
|
344
|
+
p.add_argument("urls", nargs="+", help="URL 列表(1-10 个)")
|
|
345
|
+
p.add_argument("--kb_id", default=os.environ.get("IMA_KB_ID"), help="知识库 ID(默认读取环境变量 IMA_KB_ID)")
|
|
346
|
+
p.add_argument("--folder_id", help="文件夹 ID")
|
|
347
|
+
p.set_defaults(func=_kb_import_url)
|
|
348
|
+
|
|
349
|
+
# kb addable
|
|
350
|
+
p = kb_sub.add_parser("addable", help="列出可添加的知识库")
|
|
351
|
+
p.add_argument("--all", action="store_true", help="获取全部结果")
|
|
352
|
+
p.set_defaults(func=_kb_addable)
|
|
353
|
+
|
|
354
|
+
# ── note 模块 ─────────────────────────────────────────────────────────
|
|
355
|
+
|
|
356
|
+
note_parser = subparsers.add_parser("note", help="笔记操作")
|
|
357
|
+
note_sub = note_parser.add_subparsers(dest="command", help="笔记命令")
|
|
358
|
+
|
|
359
|
+
# note search
|
|
360
|
+
p = note_sub.add_parser("search", help="搜索笔记")
|
|
361
|
+
p.add_argument("query", help="搜索关键词")
|
|
362
|
+
p.add_argument(
|
|
363
|
+
"--search_type", type=int, default=0, help="检索方式: 0=标题, 1=正文"
|
|
364
|
+
)
|
|
365
|
+
p.add_argument(
|
|
366
|
+
"--sort_type",
|
|
367
|
+
type=int,
|
|
368
|
+
default=0,
|
|
369
|
+
help="排序: 0=更新时间, 1=创建时间, 2=标题, 3=大小",
|
|
370
|
+
)
|
|
371
|
+
p.add_argument("--start", type=int, default=0, help="翻页起始位置")
|
|
372
|
+
p.add_argument("--end", type=int, default=20, help="翻页结束位置")
|
|
373
|
+
p.add_argument("--all", action="store_true", help="获取全部结果")
|
|
374
|
+
p.set_defaults(func=_note_search)
|
|
375
|
+
|
|
376
|
+
# note folders
|
|
377
|
+
p = note_sub.add_parser("folders", help="列出笔记本")
|
|
378
|
+
p.add_argument("--limit", type=int, default=50, help="数量限制")
|
|
379
|
+
p.add_argument("--all", action="store_true", help="获取全部")
|
|
380
|
+
p.set_defaults(func=_note_folders)
|
|
381
|
+
|
|
382
|
+
# note list
|
|
383
|
+
p = note_sub.add_parser("list", help="列出笔记")
|
|
384
|
+
p.add_argument("--folder_id", help="笔记本 ID")
|
|
385
|
+
p.add_argument("--limit", type=int, default=50, help="数量限制")
|
|
386
|
+
p.add_argument("--all", action="store_true", help="获取全部")
|
|
387
|
+
p.set_defaults(func=_note_list)
|
|
388
|
+
|
|
389
|
+
# note create
|
|
390
|
+
p = note_sub.add_parser("create", help="创建笔记")
|
|
391
|
+
p.add_argument("content", help="笔记内容(Markdown)或文件路径")
|
|
392
|
+
p.add_argument("--folder_id", help="笔记本 ID")
|
|
393
|
+
p.set_defaults(func=_note_create)
|
|
394
|
+
|
|
395
|
+
# note append
|
|
396
|
+
p = note_sub.add_parser("append", help="追加内容到笔记")
|
|
397
|
+
p.add_argument("doc_id", help="目标笔记 ID")
|
|
398
|
+
p.add_argument("content", help="追加内容(Markdown)或文件路径")
|
|
399
|
+
p.set_defaults(func=_note_append)
|
|
400
|
+
|
|
401
|
+
# note read
|
|
402
|
+
p = note_sub.add_parser("read", help="读取笔记内容")
|
|
403
|
+
p.add_argument("doc_id", help="笔记 ID")
|
|
404
|
+
p.add_argument(
|
|
405
|
+
"--format", type=int, default=0, help="目标格式: 0=纯文本, 2=JSON"
|
|
406
|
+
)
|
|
407
|
+
p.add_argument("--raw", action="store_true", help="直接输出内容(不包裹 JSON)")
|
|
408
|
+
p.set_defaults(func=_note_read)
|
|
409
|
+
|
|
410
|
+
return parser
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def main() -> None:
|
|
414
|
+
"""命令行入口函数。"""
|
|
415
|
+
parser = build_parser()
|
|
416
|
+
args = parser.parse_args()
|
|
417
|
+
|
|
418
|
+
# 处理 verbose 开关
|
|
419
|
+
if args.verbose:
|
|
420
|
+
from . import logger
|
|
421
|
+
logger.enable()
|
|
422
|
+
|
|
423
|
+
if not args.module:
|
|
424
|
+
parser.print_help()
|
|
425
|
+
sys.exit(1)
|
|
426
|
+
|
|
427
|
+
if not args.command:
|
|
428
|
+
# 打印子模块帮助
|
|
429
|
+
if args.module == "kb":
|
|
430
|
+
parser.parse_args(["kb", "-h"])
|
|
431
|
+
elif args.module == "note":
|
|
432
|
+
parser.parse_args(["note", "-h"])
|
|
433
|
+
sys.exit(1)
|
|
434
|
+
|
|
435
|
+
if not hasattr(args, "func"):
|
|
436
|
+
parser.print_help()
|
|
437
|
+
sys.exit(1)
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
args.func(args)
|
|
441
|
+
except Exception as e:
|
|
442
|
+
print(f"错误: {e}", file=sys.stderr)
|
|
443
|
+
sys.exit(1)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
if __name__ == "__main__":
|
|
447
|
+
main()
|