ark-utils-cli 0.3.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.
ark_cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """火山引擎命令行工具"""
2
+
3
+ __version__ = "0.3.0"
ark_cli/cli.py ADDED
@@ -0,0 +1,268 @@
1
+ """命令行入口"""
2
+
3
+ import argparse
4
+ import sys
5
+
6
+ from ark_cli import __version__
7
+
8
+
9
+ def handle_tos(args):
10
+ """处理 TOS 相关命令"""
11
+ from ark_cli.tos_client import TosClient
12
+
13
+ try:
14
+ client = TosClient()
15
+ except ValueError as e:
16
+ print(f"错误: {e}", file=sys.stderr)
17
+ sys.exit(1)
18
+
19
+ if args.action == "upload":
20
+ if not all([args.bucket, args.input, args.output]):
21
+ print("错误: upload 操作需要 -bucket, -i, -o 参数", file=sys.stderr)
22
+ sys.exit(1)
23
+
24
+ try:
25
+ url = client.upload(args.bucket, args.input, args.output)
26
+ print(url)
27
+ except FileNotFoundError as e:
28
+ print(f"错误: {e}", file=sys.stderr)
29
+ sys.exit(1)
30
+ except Exception as e:
31
+ print(f"上传失败: {e}", file=sys.stderr)
32
+ sys.exit(1)
33
+
34
+ elif args.action == "ls":
35
+ if not args.bucket:
36
+ print("错误: ls 操作需要 -bucket 参数", file=sys.stderr)
37
+ sys.exit(1)
38
+
39
+ try:
40
+ prefix = args.input or ""
41
+ # TOS 对象 key 不以 / 开头,/ 表示根目录
42
+ if prefix == "/":
43
+ prefix = ""
44
+ # 移除开头的 /
45
+ prefix = prefix.lstrip("/")
46
+ items = client.list_objects(args.bucket, prefix=prefix)
47
+
48
+ if not items:
49
+ print("(空)")
50
+ return
51
+
52
+ # 格式化输出
53
+ for item in items:
54
+ if item["type"] == "dir":
55
+ print(f"{'DIR':<10} {'-':<20} {item['key']}")
56
+ else:
57
+ size_str = _format_size(item["size"])
58
+ print(f"{size_str:<10} {item['last_modified']:<20} {item['key']}")
59
+ except Exception as e:
60
+ print(f"列表失败: {e}", file=sys.stderr)
61
+ sys.exit(1)
62
+
63
+ elif args.action == "geturl":
64
+ if not all([args.bucket, args.input]):
65
+ print("错误: geturl 操作需要 -bucket, -i 参数", file=sys.stderr)
66
+ sys.exit(1)
67
+
68
+ try:
69
+ url = client.get_url(args.bucket, args.input)
70
+ print(url)
71
+ except Exception as e:
72
+ print(f"获取链接失败: {e}", file=sys.stderr)
73
+ sys.exit(1)
74
+
75
+ elif args.action == "lsb":
76
+ try:
77
+ items = client.list_buckets()
78
+
79
+ if not items:
80
+ print("(空)")
81
+ return
82
+
83
+ for item in items:
84
+ print(f"{item['creation_date']:<20} {item['name']}")
85
+ except Exception as e:
86
+ print(f"列表失败: {e}", file=sys.stderr)
87
+ sys.exit(1)
88
+
89
+ elif args.action == "rm":
90
+ if not all([args.bucket, args.input]):
91
+ print("错误: rm 操作需要 -bucket, -i 参数", file=sys.stderr)
92
+ sys.exit(1)
93
+
94
+ try:
95
+ client.delete(args.bucket, args.input)
96
+ print(f"已删除: {args.input}")
97
+ except Exception as e:
98
+ print(f"删除失败: {e}", file=sys.stderr)
99
+ sys.exit(1)
100
+
101
+ elif args.action == "stats":
102
+ if not args.bucket:
103
+ print("错误: stats 操作需要 -bucket 参数", file=sys.stderr)
104
+ sys.exit(1)
105
+
106
+ try:
107
+ info = client.stats(args.bucket)
108
+ print(f"Bucket: {info['name']}")
109
+ print(f"Location: {info['location']}")
110
+ print(f"Created: {info['creation_date']}")
111
+ print(f"Storage: {info['storage_class']}")
112
+ print(f"Redundancy: {info['az_redundancy']}")
113
+ print(f"Versioning: {info['versioning']}")
114
+ print(f"Endpoint: {info['extranet_endpoint']}")
115
+ except Exception as e:
116
+ print(f"获取统计失败: {e}", file=sys.stderr)
117
+ sys.exit(1)
118
+
119
+ else:
120
+ print(f"错误: 不支持的操作: {args.action}", file=sys.stderr)
121
+ sys.exit(1)
122
+
123
+
124
+ def _format_size(size: int) -> str:
125
+ """格式化文件大小"""
126
+ for unit in ["B", "KB", "MB", "GB", "TB"]:
127
+ if size < 1024:
128
+ return f"{size:.1f}{unit}" if unit != "B" else f"{size}{unit}"
129
+ size /= 1024
130
+ return f"{size:.1f}PB"
131
+
132
+
133
+ def handle_model(args):
134
+ """处理大模型相关命令"""
135
+ from ark_cli.model_client import ModelClient
136
+
137
+ if not all([args.model, args.content]):
138
+ print("错误: model 产品需要 -m 和 -c 参数", file=sys.stderr)
139
+ sys.exit(1)
140
+
141
+ try:
142
+ client = ModelClient()
143
+ result = client.chat(args.model, args.content)
144
+ print(result.content)
145
+ print()
146
+ print(f"[耗时: {result.duration_ms}ms | tokens: {result.input_tokens} in / {result.output_tokens} out]")
147
+ except ValueError as e:
148
+ print(f"错误: {e}", file=sys.stderr)
149
+ sys.exit(1)
150
+ except Exception as e:
151
+ print(f"调用失败: {e}", file=sys.stderr)
152
+ sys.exit(1)
153
+
154
+
155
+ def handle_search(args):
156
+ """处理联网搜索命令"""
157
+ from ark_cli.search_client import SearchClient
158
+
159
+ if not args.query:
160
+ print("错误: search 产品需要 -q 参数", file=sys.stderr)
161
+ sys.exit(1)
162
+
163
+ try:
164
+ client = SearchClient()
165
+ result = client.search(
166
+ query=args.query,
167
+ count=args.count or 10,
168
+ time_range=args.time_range,
169
+ )
170
+
171
+ if not result.results:
172
+ print("未找到相关结果")
173
+ return
174
+
175
+ for i, item in enumerate(result.results, 1):
176
+ print(f"[{i}] {item.title}")
177
+ print(f" {item.site_name} | {item.publish_time[:10] if item.publish_time else '-'}")
178
+ print(f" {item.url}")
179
+ if item.snippet:
180
+ # 截取摘要前 150 字符
181
+ snippet = item.snippet[:150] + "..." if len(item.snippet) > 150 else item.snippet
182
+ print(f" {snippet}")
183
+ print()
184
+
185
+ print(f"[共 {result.result_count} 条结果 | 耗时: {result.duration_ms}ms]")
186
+
187
+ except ValueError as e:
188
+ print(f"错误: {e}", file=sys.stderr)
189
+ sys.exit(1)
190
+ except Exception as e:
191
+ print(f"搜索失败: {e}", file=sys.stderr)
192
+ sys.exit(1)
193
+
194
+
195
+ def main():
196
+ parser = argparse.ArgumentParser(
197
+ prog="ark-cli",
198
+ description=f"火山引擎命令行工具 v{__version__} - 让人类和 AI Agent 都能在终端中操作火山引擎",
199
+ )
200
+ parser.add_argument(
201
+ "-v", "--version",
202
+ action="version",
203
+ version=f"%(prog)s {__version__}",
204
+ )
205
+ parser.add_argument(
206
+ "-p", "--product",
207
+ choices=["tos", "model", "search"],
208
+ help="产品名称",
209
+ )
210
+ parser.add_argument(
211
+ "-a", "--action",
212
+ choices=["upload", "ls", "lsb", "geturl", "rm", "stats"],
213
+ help="操作类型",
214
+ )
215
+ parser.add_argument(
216
+ "-bucket",
217
+ help="bucket 名称",
218
+ )
219
+ parser.add_argument(
220
+ "-i", "--input",
221
+ help="路径(本地文件或云上对象 key)",
222
+ )
223
+ parser.add_argument(
224
+ "-o", "--output",
225
+ help="云上对象 key(仅 upload 使用)",
226
+ )
227
+ parser.add_argument(
228
+ "-m", "--model",
229
+ help="模型名称 (例如: doubao-seed-2-0-mini-260215)",
230
+ )
231
+ parser.add_argument(
232
+ "-c", "--content",
233
+ help="对话内容",
234
+ )
235
+ parser.add_argument(
236
+ "-q", "--query",
237
+ help="搜索关键词",
238
+ )
239
+ parser.add_argument(
240
+ "--count",
241
+ type=int,
242
+ default=10,
243
+ help="搜索返回条数 (默认: 10, 最多: 50)",
244
+ )
245
+ parser.add_argument(
246
+ "--time-range",
247
+ choices=["OneDay", "OneWeek", "OneMonth", "OneYear"],
248
+ default="OneWeek",
249
+ help="搜索时间范围 (默认: OneWeek)",
250
+ )
251
+
252
+ args = parser.parse_args()
253
+
254
+ if args.product == "tos":
255
+ handle_tos(args)
256
+ elif args.product == "model":
257
+ handle_model(args)
258
+ elif args.product == "search":
259
+ handle_search(args)
260
+ elif args.product:
261
+ print(f"错误: 不支持的产品: {args.product}", file=sys.stderr)
262
+ sys.exit(1)
263
+ else:
264
+ parser.print_help()
265
+
266
+
267
+ if __name__ == "__main__":
268
+ main()
@@ -0,0 +1,90 @@
1
+ """大模型客户端"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import time
8
+ import urllib.request
9
+ import urllib.error
10
+ from dataclasses import dataclass
11
+
12
+
13
+ @dataclass
14
+ class ChatResult:
15
+ """对话结果"""
16
+
17
+ content: str
18
+ duration_ms: int
19
+ input_tokens: int
20
+ output_tokens: int
21
+
22
+ def __str__(self) -> str:
23
+ return self.content
24
+
25
+
26
+ class ModelClient:
27
+ """火山引擎大模型客户端"""
28
+
29
+ DEFAULT_ENDPOINT = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"
30
+
31
+ def __init__(self, api_key: str | None = None, endpoint: str | None = None):
32
+ self.api_key = api_key or os.environ.get("ARK_API_KEY")
33
+ self.endpoint = endpoint or os.environ.get("ARK_ENDPOINT") or self.DEFAULT_ENDPOINT
34
+
35
+ if not self.api_key:
36
+ raise ValueError("缺少配置,请设置环境变量: ARK_API_KEY")
37
+
38
+ def chat(self, model: str, content: str) -> ChatResult:
39
+ """进行一轮对话
40
+
41
+ Args:
42
+ model: 模型名称
43
+ content: 用户输入内容
44
+
45
+ Returns:
46
+ ChatResult 包含回复内容、耗时和 token 使用量
47
+ """
48
+ data = {
49
+ "model": model,
50
+ "messages": [
51
+ {
52
+ "role": "user",
53
+ "content": [
54
+ {
55
+ "type": "text",
56
+ "text": content,
57
+ }
58
+ ],
59
+ }
60
+ ],
61
+ }
62
+
63
+ headers = {
64
+ "Content-Type": "application/json",
65
+ "Authorization": f"Bearer {self.api_key}",
66
+ }
67
+
68
+ req = urllib.request.Request(
69
+ self.endpoint,
70
+ data=json.dumps(data).encode("utf-8"),
71
+ headers=headers,
72
+ method="POST",
73
+ )
74
+
75
+ try:
76
+ start_time = time.time()
77
+ with urllib.request.urlopen(req) as response:
78
+ result = json.loads(response.read().decode("utf-8"))
79
+ duration_ms = int((time.time() - start_time) * 1000)
80
+
81
+ usage = result.get("usage", {})
82
+ return ChatResult(
83
+ content=result["choices"][0]["message"]["content"],
84
+ duration_ms=duration_ms,
85
+ input_tokens=usage.get("prompt_tokens", 0),
86
+ output_tokens=usage.get("completion_tokens", 0),
87
+ )
88
+ except urllib.error.HTTPError as e:
89
+ error_body = e.read().decode("utf-8")
90
+ raise RuntimeError(f"API 调用失败 ({e.code}): {error_body}")
@@ -0,0 +1,126 @@
1
+ """联网搜索客户端"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import time
8
+ import urllib.request
9
+ import urllib.error
10
+ from dataclasses import dataclass
11
+
12
+
13
+ @dataclass
14
+ class SearchResult:
15
+ """搜索结果项"""
16
+
17
+ title: str
18
+ site_name: str
19
+ url: str
20
+ snippet: str
21
+ publish_time: str
22
+
23
+
24
+ @dataclass
25
+ class SearchResponse:
26
+ """搜索响应"""
27
+
28
+ results: list[SearchResult]
29
+ result_count: int
30
+ duration_ms: int
31
+ query: str
32
+
33
+
34
+ class SearchClient:
35
+ """火山引擎联网搜索客户端"""
36
+
37
+ DEFAULT_ENDPOINT = "https://open.feedcoopapi.com/search_api/web_search"
38
+
39
+ def __init__(self, api_key: str | None = None, endpoint: str | None = None):
40
+ self.api_key = api_key or os.environ.get("SEARCH_API_KEY")
41
+ self.endpoint = endpoint or os.environ.get("SEARCH_ENDPOINT") or self.DEFAULT_ENDPOINT
42
+
43
+ if not self.api_key:
44
+ raise ValueError("缺少配置,请设置环境变量: SEARCH_API_KEY")
45
+
46
+ def search(
47
+ self,
48
+ query: str,
49
+ search_type: str = "web",
50
+ count: int = 10,
51
+ time_range: str = "OneWeek",
52
+ ) -> SearchResponse:
53
+ """执行搜索
54
+
55
+ Args:
56
+ query: 搜索关键词 (1-100字符)
57
+ search_type: 搜索类型 (web/web_summary/image)
58
+ count: 返回条数 (最多50条,默认10条)
59
+ time_range: 时间范围 (OneDay/OneWeek/OneMonth/OneYear)
60
+
61
+ Returns:
62
+ SearchResponse 包含搜索结果列表和统计信息
63
+ """
64
+ data: dict = {
65
+ "Query": query,
66
+ "SearchType": search_type,
67
+ "Count": count,
68
+ "Filter": {
69
+ "NeedUrl": True,
70
+ "AuthInfoLevel": 1,
71
+ "NeedContent": True
72
+ },
73
+ }
74
+
75
+ data["TimeRange"] = time_range
76
+
77
+ headers = {
78
+ "Content-Type": "application/json",
79
+ "Authorization": f"Bearer {self.api_key}",
80
+ }
81
+
82
+ req = urllib.request.Request(
83
+ self.endpoint,
84
+ data=json.dumps(data).encode("utf-8"),
85
+ headers=headers,
86
+ method="POST",
87
+ )
88
+
89
+ try:
90
+ start_time = time.time()
91
+ with urllib.request.urlopen(req) as response:
92
+ result = json.loads(response.read().decode("utf-8"))
93
+ duration_ms = int((time.time() - start_time) * 1000)
94
+
95
+ # 检查错误
96
+ if "ResponseMetadata" in result:
97
+ metadata = result["ResponseMetadata"]
98
+ if "Error" in metadata:
99
+ error = metadata["Error"]
100
+ raise RuntimeError(f"API 错误 ({error.get('CodeN', 'unknown')}): {error.get('Message', 'unknown error')}")
101
+
102
+ # 解析结果
103
+ api_result = result.get("Result", {})
104
+ web_results = api_result.get("WebResults", []) or []
105
+ search_context = api_result.get("SearchContext", {})
106
+
107
+ results = []
108
+ for item in web_results:
109
+ results.append(SearchResult(
110
+ title=item.get("Title", ""),
111
+ site_name=item.get("SiteName", ""),
112
+ url=item.get("Url", ""),
113
+ snippet=item.get("Snippet", ""),
114
+ publish_time=item.get("PublishTime", ""),
115
+ ))
116
+
117
+ return SearchResponse(
118
+ results=results,
119
+ result_count=api_result.get("ResultCount", 0),
120
+ duration_ms=duration_ms,
121
+ query=search_context.get("OriginQuery", query),
122
+ )
123
+
124
+ except urllib.error.HTTPError as e:
125
+ error_body = e.read().decode("utf-8")
126
+ raise RuntimeError(f"API 调用失败 ({e.code}): {error_body}")
ark_cli/tos_client.py ADDED
@@ -0,0 +1,185 @@
1
+ """TOS 对象存储客户端"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ import tos
8
+
9
+
10
+ class TosClient:
11
+ """火山引擎 TOS 对象存储客户端"""
12
+
13
+ DEFAULT_ENDPOINT = "tos-cn-beijing.volces.com"
14
+ DEFAULT_REGION = "cn-beijing"
15
+
16
+ def __init__(
17
+ self,
18
+ ak: str | None = None,
19
+ sk: str | None = None,
20
+ endpoint: str | None = None,
21
+ region: str | None = None,
22
+ ):
23
+ self.ak = ak or os.environ.get("TOS_ACCESS_KEY")
24
+ self.sk = sk or os.environ.get("TOS_SECRET_KEY")
25
+ self.endpoint = endpoint or os.environ.get("TOS_ENDPOINT") or self.DEFAULT_ENDPOINT
26
+ self.region = region or os.environ.get("TOS_REGION") or self.DEFAULT_REGION
27
+
28
+ if not all([self.ak, self.sk]):
29
+ raise ValueError(
30
+ "缺少 TOS 配置,请设置环境变量: TOS_ACCESS_KEY, TOS_SECRET_KEY"
31
+ )
32
+
33
+ self.client = tos.TosClientV2(
34
+ ak=self.ak,
35
+ sk=self.sk,
36
+ endpoint=self.endpoint,
37
+ region=self.region,
38
+ )
39
+
40
+ def upload(self, bucket: str, local_path: str, object_key: str, expires: int = 86400) -> str:
41
+ """上传文件到 TOS
42
+
43
+ Args:
44
+ bucket: bucket 名称
45
+ local_path: 本地文件路径
46
+ object_key: 云上对象 key
47
+ expires: 临时链接过期时间(秒),默认 24 小时
48
+
49
+ Returns:
50
+ 带签名的临时访问链接
51
+ """
52
+ if not os.path.exists(local_path):
53
+ raise FileNotFoundError(f"文件不存在: {local_path}")
54
+
55
+ self.client.put_object_from_file(bucket, object_key, local_path)
56
+
57
+ # 生成预签名临时链接
58
+ result = self.client.pre_signed_url(
59
+ http_method=tos.HttpMethodType.Http_Method_Get,
60
+ bucket=bucket,
61
+ key=object_key,
62
+ expires=expires,
63
+ )
64
+
65
+ return result.signed_url
66
+
67
+ def list_objects(self, bucket: str, prefix: str = "", max_keys: int = 1000) -> list[dict]:
68
+ """列出指定路径下的文件
69
+
70
+ Args:
71
+ bucket: bucket 名称
72
+ prefix: 路径前缀
73
+ max_keys: 最大返回数量
74
+
75
+ Returns:
76
+ 文件列表,每个元素包含 key, size, last_modified
77
+ """
78
+ result = self.client.list_objects(
79
+ bucket,
80
+ prefix=prefix,
81
+ max_keys=max_keys,
82
+ delimiter="/",
83
+ )
84
+
85
+ items = []
86
+
87
+ # 添加目录(common_prefixes)
88
+ for cp in result.common_prefixes:
89
+ items.append({
90
+ "key": cp.prefix,
91
+ "size": "-",
92
+ "last_modified": "-",
93
+ "type": "dir",
94
+ })
95
+
96
+ # 添加文件
97
+ for obj in result.contents:
98
+ # 跳过与 prefix 完全相同的项(目录本身)
99
+ if obj.key == prefix:
100
+ continue
101
+ items.append({
102
+ "key": obj.key,
103
+ "size": obj.size,
104
+ "last_modified": obj.last_modified.strftime("%Y-%m-%d %H:%M:%S"),
105
+ "type": "file",
106
+ })
107
+
108
+ return items
109
+
110
+ def get_url(self, bucket: str, object_key: str, expires: int = 86400) -> str:
111
+ """获取对象的临时访问链接
112
+
113
+ Args:
114
+ bucket: bucket 名称
115
+ object_key: 云上对象 key
116
+ expires: 临时链接过期时间(秒),默认 24 小时
117
+
118
+ Returns:
119
+ 带签名的临时访问链接
120
+ """
121
+ result = self.client.pre_signed_url(
122
+ http_method=tos.HttpMethodType.Http_Method_Get,
123
+ bucket=bucket,
124
+ key=object_key,
125
+ expires=expires,
126
+ )
127
+
128
+ return result.signed_url
129
+
130
+ def list_buckets(self) -> list[dict]:
131
+ """列出所有 bucket
132
+
133
+ Returns:
134
+ bucket 列表,每个元素包含 name, creation_date
135
+ """
136
+ result = self.client.list_buckets()
137
+
138
+ items = []
139
+ for bucket in result.buckets:
140
+ creation_date = bucket.creation_date
141
+ # 兼容 datetime 对象和字符串
142
+ if hasattr(creation_date, "strftime"):
143
+ creation_date = creation_date.strftime("%Y-%m-%d %H:%M:%S")
144
+ items.append({
145
+ "name": bucket.name,
146
+ "creation_date": creation_date,
147
+ })
148
+
149
+ return items
150
+
151
+ def delete(self, bucket: str, object_key: str) -> None:
152
+ """删除对象
153
+
154
+ Args:
155
+ bucket: bucket 名称
156
+ object_key: 云上对象 key
157
+ """
158
+ self.client.delete_object(bucket, object_key)
159
+
160
+ def stats(self, bucket: str) -> dict:
161
+ """获取 bucket 元数据
162
+
163
+ Args:
164
+ bucket: bucket 名称
165
+
166
+ Returns:
167
+ 包含 bucket 元数据的字典
168
+ """
169
+ result = self.client.get_bucket_info(bucket)
170
+ info = result.bucket_info
171
+
172
+ creation_date = info.creation_date
173
+ if hasattr(creation_date, "strftime"):
174
+ creation_date = creation_date.strftime("%Y-%m-%d %H:%M:%S")
175
+
176
+ return {
177
+ "name": info.name,
178
+ "location": info.location,
179
+ "creation_date": creation_date,
180
+ "storage_class": str(info.storage_class).replace("StorageClassType.", ""),
181
+ "az_redundancy": str(info.az_redundancy).replace("AzRedundancyType.", ""),
182
+ "versioning": str(info.versioning).replace("VersioningStatusType.", ""),
183
+ "extranet_endpoint": info.extranet_endpoint,
184
+ "intranet_endpoint": info.intranet_endpoint,
185
+ }
@@ -0,0 +1,277 @@
1
+ Metadata-Version: 2.4
2
+ Name: ark-utils-cli
3
+ Version: 0.3.0
4
+ Summary: 火山引擎命令行工具 - 让人类和 AI Agent 都能在终端中操作火山引擎
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: tos>=2.9.0
8
+ Description-Content-Type: text/markdown
9
+
10
+ <p align="center">
11
+ <h1 align="center">ark-cli</h1>
12
+ </p>
13
+
14
+ <p align="center">
15
+ 火山引擎命令行工具 — 让人类和 AI Agent 都能在终端中操作火山引擎
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="https://github.com/cnwarden/ark-cli/releases"><img src="https://img.shields.io/github/v/release/cnwarden/ark-cli?style=for-the-badge&color=FF6A00" alt="Release" /></a>
20
+ <a href="https://www.python.org/"><img src="https://img.shields.io/badge/Python-3.9+-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python" /></a>
21
+ <a href="https://github.com/cnwarden/ark-cli/stargazers"><img src="https://img.shields.io/github/stars/cnwarden/ark-cli?style=for-the-badge&color=f5a623" alt="Stars" /></a>
22
+ <a href="https://github.com/cnwarden/ark-cli/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-teal.svg?style=for-the-badge" alt="License" /></a>
23
+ </p>
24
+
25
+ <p align="center">
26
+ <a href="#ark-cli-是什么">介绍</a> · <a href="#核心能力">核心能力</a> · <a href="#快速开始">快速开始</a> · <a href="#命令参考">命令参考</a> · <a href="#贡献">贡献</a>
27
+ </p>
28
+
29
+ ---
30
+
31
+ ## ark-cli 是什么
32
+
33
+ ark-cli 是火山引擎的命令行工具,将 TOS 对象存储、大模型服务、联网搜索等操作封装为简洁的命令行接口。
34
+
35
+ ### 为什么选择 ark-cli
36
+
37
+ - **开箱即用** — 默认配置北京区域,最少只需 2 个环境变量即可开始使用
38
+ - **AI Agent 友好** — 命令设计简洁,输出格式规范,便于 AI Agent 解析调用
39
+ - **临时链接** — 上传文件自动返回 24 小时有效的签名 URL
40
+ - **轻量依赖** — 基于 Python,使用 uv 包管理,安装简单
41
+
42
+ ## 核心能力
43
+
44
+ | 模块 | 能力 |
45
+ |------|------|
46
+ | **TOS 对象存储** | 文件上传、列表、删除、临时链接生成、Bucket 统计 |
47
+ | **大模型服务** | 单轮对话、Token 用量统计、耗时统计 |
48
+ | **联网搜索** | 网页搜索、结果摘要、时间范围过滤 |
49
+
50
+ ## 快速开始
51
+
52
+ ### 安装
53
+
54
+ **从源码安装**
55
+
56
+ ```bash
57
+ git clone https://github.com/cnwarden/ark-cli.git
58
+ cd ark-cli
59
+ uv sync
60
+ ```
61
+
62
+ **从 Release 下载**
63
+
64
+ 从 [Releases](https://github.com/cnwarden/ark-cli/releases/latest) 页面下载最新的 wheel 包:
65
+
66
+ ```bash
67
+ pip install ark_cli-*.whl
68
+ ```
69
+
70
+ ### 配置凭证
71
+
72
+ ```bash
73
+ # TOS 对象存储(必需)
74
+ export TOS_ACCESS_KEY="your-access-key"
75
+ export TOS_SECRET_KEY="your-secret-key"
76
+
77
+ # TOS 可选配置(默认北京区域)
78
+ export TOS_ENDPOINT="tos-cn-beijing.volces.com"
79
+ export TOS_REGION="cn-beijing"
80
+
81
+ # 大模型服务(使用 model 功能时必需)
82
+ export ARK_API_KEY="your-api-key"
83
+
84
+ # 联网搜索(使用 search 功能时必需)
85
+ export SEARCH_API_KEY="your-search-api-key"
86
+ ```
87
+
88
+ ### 验证安装
89
+
90
+ ```bash
91
+ ark-cli -v
92
+ ```
93
+
94
+ ## 命令参考
95
+
96
+ ```
97
+ ark-cli [-p PRODUCT] [-a ACTION] [OPTIONS]
98
+
99
+ Products:
100
+ tos TOS 对象存储操作
101
+ model 大模型对话
102
+ search 联网搜索
103
+
104
+ TOS Actions:
105
+ upload 上传文件
106
+ ls 列出文件
107
+ lsb 列出 Bucket
108
+ geturl 获取临时链接
109
+ rm 删除文件
110
+ stats Bucket 统计信息
111
+
112
+ Model Options:
113
+ -m 模型名称(例如: doubao-seed-2-0-mini-260215)
114
+ -c 对话内容
115
+
116
+ Search Options:
117
+ -q 搜索关键词
118
+ --count 返回条数(默认: 10, 最多: 50)
119
+ ```
120
+
121
+ <details>
122
+ <summary>TOS 对象存储</summary>
123
+
124
+ ```bash
125
+ # 上传文件(返回 24 小时有效的签名链接)
126
+ ark-cli -p tos -a upload -bucket my-bucket -i ./test.txt -o data/test.txt
127
+
128
+ # 列出 Bucket
129
+ ark-cli -p tos -a lsb
130
+
131
+ # 列出文件
132
+ ark-cli -p tos -a ls -bucket my-bucket
133
+ ark-cli -p tos -a ls -bucket my-bucket -i data/
134
+
135
+ # 获取临时链接
136
+ ark-cli -p tos -a geturl -bucket my-bucket -i data/test.txt
137
+
138
+ # 删除文件
139
+ ark-cli -p tos -a rm -bucket my-bucket -i data/test.txt
140
+
141
+ # Bucket 统计信息
142
+ ark-cli -p tos -a stats -bucket my-bucket
143
+ ```
144
+
145
+ **输出示例**
146
+
147
+ 列出文件:
148
+ ```
149
+ DIR - data/subdir/
150
+ 1.2KB 2024-03-30 10:00:00 data/test.txt
151
+ 256B 2024-03-30 09:30:00 data/config.json
152
+ ```
153
+
154
+ Bucket 统计:
155
+ ```
156
+ Bucket: my-bucket
157
+ Location: cn-beijing
158
+ Created: 2025-05-12 12:29:06
159
+ Storage: Storage_Class_Standard
160
+ Redundancy: Az_Redundancy_Single_Az
161
+ Versioning: Versioning_Unknown
162
+ Endpoint: tos-cn-beijing.volces.com
163
+ ```
164
+
165
+ </details>
166
+
167
+ <details>
168
+ <summary>大模型对话</summary>
169
+
170
+ ```bash
171
+ # 单轮对话
172
+ ark-cli -p model -m doubao-seed-2-0-mini-260215 -c "你好,请介绍一下你自己"
173
+ ```
174
+
175
+ **输出示例**
176
+ ```
177
+ 嗨!👋 你好呀~ 请问有什么我可以帮你的吗?
178
+
179
+ [耗时: 5991ms | tokens: 50 in / 371 out]
180
+ ```
181
+
182
+ </details>
183
+
184
+ <details>
185
+ <summary>联网搜索</summary>
186
+
187
+ ```bash
188
+ # 基础搜索
189
+ ark-cli -p search -q "火山引擎"
190
+
191
+ # 指定返回条数
192
+ ark-cli -p search -q "Python 教程" --count 5
193
+ ```
194
+
195
+ **输出示例**
196
+ ```
197
+ [1] 火山引擎官网 - 字节跳动旗下云服务平台
198
+ 火山引擎 | 2025-03-30
199
+ https://www.volcengine.com/
200
+ 火山引擎是字节跳动旗下的云服务平台,提供云计算、大数据、人工智能等服务...
201
+
202
+ [2] 火山引擎开放平台 - 开发者文档
203
+ 火山引擎 | 2025-03-28
204
+ https://www.volcengine.com/docs
205
+ 火山引擎开放平台提供丰富的 API 和 SDK,帮助开发者快速接入...
206
+
207
+ [共 10 条结果 | 耗时: 372ms]
208
+ ```
209
+
210
+ </details>
211
+
212
+ ## 技术栈
213
+
214
+ | 组件 | 选型 | 说明 |
215
+ |------|------|------|
216
+ | 语言 | [Python](https://www.python.org/) 3.9+ | |
217
+ | 包管理 | [uv](https://github.com/astral-sh/uv) | 快速的 Python 包管理器 |
218
+ | TOS SDK | [tos](https://github.com/volcengine/ve-tos-python-sdk) | 火山引擎 TOS 官方 SDK |
219
+ | 构建工具 | [hatchling](https://hatch.pypa.io/) | Python 构建后端 |
220
+
221
+ ## 项目结构
222
+
223
+ ```
224
+ ark-cli/
225
+ ├── ark_cli/
226
+ │ ├── __init__.py # 版本定义
227
+ │ ├── cli.py # CLI 入口
228
+ │ ├── tos_client.py # TOS 客户端封装
229
+ │ ├── model_client.py # 大模型客户端
230
+ │ └── search_client.py # 联网搜索客户端
231
+ ├── pyproject.toml # 项目配置
232
+ ├── Makefile # 构建脚本
233
+ └── README.md
234
+ ```
235
+
236
+ ## 开发
237
+
238
+ ```bash
239
+ # 克隆项目
240
+ git clone https://github.com/cnwarden/ark-cli.git
241
+ cd ark-cli
242
+
243
+ # 安装依赖
244
+ uv sync
245
+
246
+ # 运行
247
+ uv run ark-cli -v
248
+
249
+ # 构建
250
+ uv build
251
+
252
+ # 发布到 TOS
253
+ make publish
254
+ ```
255
+
256
+ ## 贡献
257
+
258
+ 欢迎提交 Issue 和 Pull Request!
259
+
260
+ 1. Fork 本仓库
261
+ 2. 创建特性分支:`git checkout -b feature/amazing-feature`
262
+ 3. 提交更改:`git commit -m 'feat: add amazing feature'`
263
+ 4. 推送分支:`git push origin feature/amazing-feature`
264
+ 5. 提交 Pull Request
265
+
266
+ 提交信息请遵循 [Conventional Commits](https://www.conventionalcommits.org/) 规范。
267
+
268
+ ## License
269
+
270
+ [MIT](LICENSE)
271
+
272
+ ## 相关链接
273
+
274
+ - [火山引擎控制台](https://console.volcengine.com/) — 获取 Access Key
275
+ - [TOS 文档](https://www.volcengine.com/docs/6349) — 对象存储文档
276
+ - [方舟大模型](https://www.volcengine.com/docs/82379) — 大模型服务文档
277
+ - [联网搜索 API](https://www.volcengine.com/docs/87772/2272953) — 联网搜索文档
@@ -0,0 +1,10 @@
1
+ ark_cli/__init__.py,sha256=6_kreJnX_xCuH6ty5hsJsg-F9qVjFq8xlYradAAfV-Q,57
2
+ ark_cli/cli.py,sha256=xGkp4w06N66b8LLwzXcn2dNPSa4szwIhXMUYd6Kf660,8124
3
+ ark_cli/model_client.py,sha256=77EQLnYbFMazcZRG8TwGSNgRDeYORyebU3r38ipaF4I,2563
4
+ ark_cli/search_client.py,sha256=J8gncQMCG_16AmVvWvLCd7WFNV7OqN489Mw7AAhUhAM,3759
5
+ ark_cli/tos_client.py,sha256=VxhM_oqTCqZ7KqC7eEvL2qjhK1TtImsQsu41PJhi4Rc,5606
6
+ ark_utils_cli-0.3.0.dist-info/METADATA,sha256=kMbbgXD2mVqPInGOG2ZiS5rDG7aITUmwRbWTlsb3EtQ,7355
7
+ ark_utils_cli-0.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
8
+ ark_utils_cli-0.3.0.dist-info/entry_points.txt,sha256=s-b0LrOd_0hzcpUfTy8ungGP0QLOhL75R6sAzpLfcyg,45
9
+ ark_utils_cli-0.3.0.dist-info/licenses/LICENSE,sha256=hpECGeLwcNtHJ51vjSm_gU6DL7LfCLnpUkA5n8MDIXs,1065
10
+ ark_utils_cli-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ark-cli = ark_cli.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cnwarden
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.