shellus-llmcmd 1.0.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.
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ .env
8
+ .env.*
9
+ gemini-output/
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: shellus-llmcmd
3
+ Version: 1.0.0
4
+ Summary: Unified LLM CLI for text, image, audio, and batch tasks
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: click>=8.1
7
+ Requires-Dist: openai>=1.0
8
+ Provides-Extra: batch
9
+ Requires-Dist: pyyaml>=6.0; extra == 'batch'
10
+ Description-Content-Type: text/markdown
11
+
12
+ ---
13
+ name: llm
14
+ description: Use when text generation, image generation or editing, audio transcription, or mixed YAML orchestration tasks need to be handled through one unified CLI entry.
15
+ ---
16
+
17
+ # Unified LLM CLI
18
+
19
+ 通过统一的 `llm.py` / `llm` 入口处理对话生成、图片生成/编辑、音频转录,以及显式的 YAML 编排调用。
20
+
21
+ ## 使用时机
22
+
23
+ 当用户有以下需求时,优先使用这个 skill:
24
+ - 文本输入、文本输出:改写、润色、总结、提取、翻译、生成文案
25
+ - 图片生成或编辑:文生图、参考图编辑、批量生成图片
26
+ - 音频转录:将音频转为文本或 SRT 字幕
27
+ - 需要一个 YAML 文件里统一编排多条 chat / image / audio 任务
28
+
29
+ > [!warning]
30
+ > 单次模式和 YAML 模式**必须显式区分**。
31
+ > - 单次:`chat` / `image` / `audio`
32
+ > - YAML:`batch <yaml_path>`
33
+ >
34
+ > 不要再使用“把 `.yaml` 当作位置参数自动识别”的旧方式。
35
+
36
+ ## 安装
37
+
38
+ ```bash
39
+ # 推荐:pip editable 安装(安装后可直接使用 llm 命令)
40
+ pip install -e ~/.claude/skills/llm
41
+
42
+ # 或直接运行脚本(向后兼容)
43
+ python3 ~/.claude/skills/llm/llm.py
44
+ ```
45
+
46
+ ## 单次调用
47
+
48
+ ### 对话
49
+
50
+ ```bash
51
+ llm chat "写一段产品介绍"
52
+ llm chat @prompt.txt -o result.md
53
+ llm chat "总结重点" -i article.md -i notes.md
54
+ llm chat "整理为 JSON" -s @system.txt
55
+ llm chat "详细描述这张图的所有细节" -r photo.jpg
56
+ llm chat "把人物脸型改成偏瘦" --edit 商务女性生图.md
57
+ llm chat "按要求改写" --edit 商务女性生图.md -o 商务女性生图.v2.md
58
+ ```
59
+
60
+ 规则:
61
+ - `prompt` 支持字面量或 `@文件`
62
+ - `-s/--system` 支持字面量或 `@文件`
63
+ - `-i/--input` 可重复传入多个文本文件,作为补充上下文
64
+ - `-r/--reference` 可传入一张图片,用于视觉理解/图片分析
65
+ - `--edit` 用于编辑目标文本文件;模型必须输出 SEARCH/REPLACE diff blocks,CLI 会自动应用
66
+ - `--edit` 不带 `-o` 时直接覆盖原文件;带 `-o` 时输出到新文件
67
+ - 非 edit 模式下,有 `-o` 时写入文件;无 `-o` 时输出到终端
68
+
69
+ ### 图片
70
+
71
+ ```bash
72
+ llm image "画一只猫"
73
+ llm image @prompt.txt -o cat.jpg
74
+ llm image "保留主体,改成极简插画风格" -r photo.jpg
75
+ llm image @prompt.txt -r ref.png -s @system.txt -o result.jpg
76
+ llm image "生成三张海报方案" -n 3 -o poster.jpg
77
+ ```
78
+
79
+ 规则:
80
+ - `prompt` 支持字面量或 `@文件`
81
+ - `-s/--system` 支持字面量或 `@文件`
82
+ - `-r/--reference` 为可选参考图
83
+ - `-n/--count` 用于控制生成数量,单命令多图会遵循 image 模式并发配置
84
+ - 多图模式会输出轻量进度:开始、每张完成、最终写入结果
85
+ - `-o poster.jpg -n 3` 时会输出为 `poster.jpg`、`poster_1.jpg`、`poster_2.jpg`
86
+ - 无 `-o` 时,默认输出到当前目录下的 `gemini-output/output_时间戳.jpg`
87
+
88
+ ### 音频
89
+
90
+ ```bash
91
+ llm audio demo.mp3
92
+ llm audio demo.mp3 -o demo.srt
93
+ llm audio demo.mp3 -p @prompt.txt
94
+ llm audio demo.mp3 -p "请转成带说话人标注的 SRT" -s @system.txt
95
+ ```
96
+
97
+ 规则:
98
+ - 主位置参数必须是音频文件路径
99
+ - `-p/--prompt` 为可选附加要求,支持字面量或 `@文件`
100
+ - `-s/--system` 支持字面量或 `@文件`
101
+ - 无 `-o` 时,默认输出为音频同目录同名 `.srt`
102
+
103
+ ## YAML 编排模式
104
+
105
+ ```bash
106
+ llm batch tasks.yaml
107
+ ```
108
+
109
+ > [!note]
110
+ > 只有 `batch` 子命令才会进入 YAML 模式。
111
+
112
+ 执行时会输出:
113
+ - 上游地址
114
+ - 任务总数和并发数
115
+ - 每个任务的开始和完成状态(含使用的模型)
116
+
117
+ ### YAML 示例
118
+
119
+ ```yaml
120
+ mode: chat
121
+ system_prompt: "你是严谨的处理助手"
122
+ output_dir: outputs
123
+
124
+ tasks:
125
+ - id: summary
126
+ prompt: "总结下面内容"
127
+ input: article.md
128
+ output: summary.md
129
+
130
+ - id: edit-prompt
131
+ prompt: "把人物脸型改成偏瘦,不要改动其他描述"
132
+ edit: 商务女性生图.md
133
+ output: 商务女性生图.v2.md
134
+
135
+ - id: hero-image
136
+ mode: image
137
+ prompt: "为产品主页生成一张极简横幅图"
138
+ system_prompt: "@prompts/image-system.txt"
139
+ count: 3
140
+ output: hero.jpg
141
+
142
+ - id: transcript
143
+ mode: audio
144
+ audio_file: meeting.mp3
145
+ prompt: "请输出标准 SRT 字幕"
146
+ ```
147
+ ```
148
+
149
+ ### YAML 字段说明
150
+
151
+ **顶层字段**(全局配置)
152
+
153
+ | 字段 | 必填 | 说明 | 可被 task 覆盖 |
154
+ |------|------|------|----------------|
155
+ | `mode` | ✓ | 默认任务类型:`chat` / `image` / `audio` | ✓ |
156
+ | `model` | | 全局模型名称,覆盖环境变量配置 | ✓ |
157
+ | `system_prompt` | | 全局 system prompt,支持 `@文件` | ✓ |
158
+ | `input` | | 全局输入文件(chat 模式),路径或路径数组 | ✓ |
159
+ | `reference` | | 全局参考图(chat/image 模式) | ✓ |
160
+ | `audio_file` | | 全局音频文件(audio 模式) | ✓ |
161
+ | `output_dir` | | 任务输出基目录,相对路径基于 YAML 所在目录 | - |
162
+ | `concurrency` | | 并发数 | - |
163
+ | `temperature` | | 温度参数(0.0-2.0) | ✓ |
164
+ | `max_output_tokens` | | 最大输出 token 数 | ✓ |
165
+ | `tasks` | ✓ | 任务数组,不能为空 | - |
166
+
167
+ **task 字段**(任务级配置)
168
+
169
+ | 字段 | 必填 | 适用模式 | 说明 |
170
+ |------|------|----------|------|
171
+ | `id` | | 全部 | 任务标识,默认 `task-序号` |
172
+ | `mode` | | 全部 | 覆盖顶层 `mode` |
173
+ | `model` | | 全部 | 覆盖顶层 `model` |
174
+ | `prompt` | | 全部 | 支持字面量或 `@文件` |
175
+ | `system_prompt` | | 全部 | 覆盖顶层 `system_prompt`,支持 `@文件` |
176
+ | `input` | | chat | 覆盖顶层 `input`,路径或路径数组 |
177
+ | `reference` | | chat / image | 覆盖顶层 `reference` |
178
+ | `audio_file` | | audio | 覆盖顶层 `audio_file` |
179
+ | `edit` | | chat | 目标文本文件,进入 diff 编辑模式 |
180
+ | `count` | | image | 图片生成数量,遵循 image 模式并发配置 |
181
+ | `output` | | 全部 | 输出路径,未指定时使用默认规则 |
182
+ | `temperature` | | 全部 | 覆盖顶层 `temperature` |
183
+ | `max_output_tokens` | | 全部 | 覆盖顶层 `max_output_tokens` |
184
+
185
+ ## 配置
186
+
187
+ 脚本从 `~/.config/llm-api/.env` 读取配置,环境变量可覆盖:
188
+
189
+ ```bash
190
+ API_KEY=your_api_key
191
+ BASE_URL=https://your-api-endpoint/v1
192
+ ```
193
+
194
+ 模型优先级:
195
+
196
+ | 模式 | 优先级 |
197
+ |------|--------|
198
+ | chat | `LLM_TEXT_MODEL` → `LLM_MODEL` → `OPENAI_CHAT_MODEL` → `OPENAI_MODEL` |
199
+ | image | `LLM_IMAGE_MODEL` → `LLM_MODEL` → `OPENAI_IMAGE_MODEL` |
200
+ | audio | `LLM_AUDIO_MODEL` → `LLM_MODEL` → `GEMINI_AUDIO_MODEL` |
201
+
202
+ 并发优先级:
203
+ - 顶层 YAML `concurrency`
204
+ - `LLM_CONCURRENCY`
205
+ - 兼容旧变量(仅单一任务类型 batch 时生效)
206
+ - 默认 `4`
207
+
208
+ ## 高级选项
209
+
210
+ 以下参数保留用于高级控制,不作为主示例默认写法:
211
+ - `-t, --temperature`
212
+ - `-m, --max-output-tokens`
213
+ - `--model`
214
+
215
+ ## 常见错误
216
+
217
+ | 问题 | 原因 | 正确方式 |
218
+ |------|------|----------|
219
+ | 把 YAML 文件直接传给 `chat` | 误以为会自动进入 batch | 使用 `batch tasks.yaml` |
220
+ | audio 命令把文本写成第二个位置参数 | audio 只接受音频文件位置参数 | 用 `-p "你的要求"` |
221
+ | image/chat 想从文件读取 prompt 但直接写路径 | 未使用 `@` 前缀 | 用 `@prompt.txt` |
222
+ | 想编辑文本文件却传给 `-r` | `-r` 仅支持图片参考 | 用 `--edit file.md` |
223
+ | batch 中 audio 任务写成 `audio` 字段 | 字段名不对 | 使用 `audio_file` |
224
+
225
+ ## --debug 位置自由
226
+
227
+ `--debug` 可以放在任意位置:
228
+
229
+ ```bash
230
+ llm --debug chat "test" # group 级别
231
+ llm chat --debug "test" # command 级别(中间)
232
+ llm chat "test" --debug # command 级别(末尾)
233
+ ```
234
+
235
+ ## 输出规则速查
236
+
237
+ | 模式 | 未指定输出时 |
238
+ |------|--------------|
239
+ | chat | 打印到终端 |
240
+ | image | `./gemini-output/output_时间戳.jpg` |
241
+ | audio | 输入音频同目录同名 `.srt` |
@@ -0,0 +1,44 @@
1
+ # shellus-llmcmd
2
+
3
+ 统一的 LLM 命令行工具,提供 `llm` 命令来处理对话生成、图片生成/编辑、音频转录和 YAML 批量任务。
4
+
5
+ ## 当前进度
6
+
7
+ - [x] chat / image / audio / batch 四个子命令
8
+ - [x] chat `--edit` diff 文件编辑模式
9
+ - [x] image `-n/--count` 多图生成与轻量进度输出
10
+ - [x] YAML batch 支持 `edit` 与 `count`
11
+ - [x] 发布 GitHub 仓库
12
+ - [ ] 发布 PyPI 包
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ pip install shellus-llmcmd
18
+ ```
19
+
20
+ 安装后命令名仍为:
21
+
22
+ ```bash
23
+ llm
24
+ ```
25
+
26
+ ## 快速开始
27
+
28
+ ```bash
29
+ llm chat "写一段产品介绍"
30
+ llm chat "把人物脸型改成偏瘦" --edit prompt.md
31
+ llm image "生成三张海报方案" -n 3 -o poster.jpg
32
+ llm audio demo.m4a -o demo.srt
33
+ llm batch tasks.yaml
34
+ ```
35
+
36
+ ## 包信息
37
+
38
+ - PyPI 包名:`shellus-llmcmd`
39
+ - CLI 命令名:`llm`
40
+ - Python 要求:`>=3.10`
41
+
42
+ ## 文档
43
+
44
+ 详细用法见 [SKILL.md](./SKILL.md)。
@@ -0,0 +1,230 @@
1
+ ---
2
+ name: llm
3
+ description: Use when text generation, image generation or editing, audio transcription, or mixed YAML orchestration tasks need to be handled through one unified CLI entry.
4
+ ---
5
+
6
+ # Unified LLM CLI
7
+
8
+ 通过统一的 `llm.py` / `llm` 入口处理对话生成、图片生成/编辑、音频转录,以及显式的 YAML 编排调用。
9
+
10
+ ## 使用时机
11
+
12
+ 当用户有以下需求时,优先使用这个 skill:
13
+ - 文本输入、文本输出:改写、润色、总结、提取、翻译、生成文案
14
+ - 图片生成或编辑:文生图、参考图编辑、批量生成图片
15
+ - 音频转录:将音频转为文本或 SRT 字幕
16
+ - 需要一个 YAML 文件里统一编排多条 chat / image / audio 任务
17
+
18
+ > [!warning]
19
+ > 单次模式和 YAML 模式**必须显式区分**。
20
+ > - 单次:`chat` / `image` / `audio`
21
+ > - YAML:`batch <yaml_path>`
22
+ >
23
+ > 不要再使用“把 `.yaml` 当作位置参数自动识别”的旧方式。
24
+
25
+ ## 安装
26
+
27
+ ```bash
28
+ # 推荐:pip editable 安装(安装后可直接使用 llm 命令)
29
+ pip install -e ~/.claude/skills/llm
30
+
31
+ # 或直接运行脚本(向后兼容)
32
+ python3 ~/.claude/skills/llm/llm.py
33
+ ```
34
+
35
+ ## 单次调用
36
+
37
+ ### 对话
38
+
39
+ ```bash
40
+ llm chat "写一段产品介绍"
41
+ llm chat @prompt.txt -o result.md
42
+ llm chat "总结重点" -i article.md -i notes.md
43
+ llm chat "整理为 JSON" -s @system.txt
44
+ llm chat "详细描述这张图的所有细节" -r photo.jpg
45
+ llm chat "把人物脸型改成偏瘦" --edit 商务女性生图.md
46
+ llm chat "按要求改写" --edit 商务女性生图.md -o 商务女性生图.v2.md
47
+ ```
48
+
49
+ 规则:
50
+ - `prompt` 支持字面量或 `@文件`
51
+ - `-s/--system` 支持字面量或 `@文件`
52
+ - `-i/--input` 可重复传入多个文本文件,作为补充上下文
53
+ - `-r/--reference` 可传入一张图片,用于视觉理解/图片分析
54
+ - `--edit` 用于编辑目标文本文件;模型必须输出 SEARCH/REPLACE diff blocks,CLI 会自动应用
55
+ - `--edit` 不带 `-o` 时直接覆盖原文件;带 `-o` 时输出到新文件
56
+ - 非 edit 模式下,有 `-o` 时写入文件;无 `-o` 时输出到终端
57
+
58
+ ### 图片
59
+
60
+ ```bash
61
+ llm image "画一只猫"
62
+ llm image @prompt.txt -o cat.jpg
63
+ llm image "保留主体,改成极简插画风格" -r photo.jpg
64
+ llm image @prompt.txt -r ref.png -s @system.txt -o result.jpg
65
+ llm image "生成三张海报方案" -n 3 -o poster.jpg
66
+ ```
67
+
68
+ 规则:
69
+ - `prompt` 支持字面量或 `@文件`
70
+ - `-s/--system` 支持字面量或 `@文件`
71
+ - `-r/--reference` 为可选参考图
72
+ - `-n/--count` 用于控制生成数量,单命令多图会遵循 image 模式并发配置
73
+ - 多图模式会输出轻量进度:开始、每张完成、最终写入结果
74
+ - `-o poster.jpg -n 3` 时会输出为 `poster.jpg`、`poster_1.jpg`、`poster_2.jpg`
75
+ - 无 `-o` 时,默认输出到当前目录下的 `gemini-output/output_时间戳.jpg`
76
+
77
+ ### 音频
78
+
79
+ ```bash
80
+ llm audio demo.mp3
81
+ llm audio demo.mp3 -o demo.srt
82
+ llm audio demo.mp3 -p @prompt.txt
83
+ llm audio demo.mp3 -p "请转成带说话人标注的 SRT" -s @system.txt
84
+ ```
85
+
86
+ 规则:
87
+ - 主位置参数必须是音频文件路径
88
+ - `-p/--prompt` 为可选附加要求,支持字面量或 `@文件`
89
+ - `-s/--system` 支持字面量或 `@文件`
90
+ - 无 `-o` 时,默认输出为音频同目录同名 `.srt`
91
+
92
+ ## YAML 编排模式
93
+
94
+ ```bash
95
+ llm batch tasks.yaml
96
+ ```
97
+
98
+ > [!note]
99
+ > 只有 `batch` 子命令才会进入 YAML 模式。
100
+
101
+ 执行时会输出:
102
+ - 上游地址
103
+ - 任务总数和并发数
104
+ - 每个任务的开始和完成状态(含使用的模型)
105
+
106
+ ### YAML 示例
107
+
108
+ ```yaml
109
+ mode: chat
110
+ system_prompt: "你是严谨的处理助手"
111
+ output_dir: outputs
112
+
113
+ tasks:
114
+ - id: summary
115
+ prompt: "总结下面内容"
116
+ input: article.md
117
+ output: summary.md
118
+
119
+ - id: edit-prompt
120
+ prompt: "把人物脸型改成偏瘦,不要改动其他描述"
121
+ edit: 商务女性生图.md
122
+ output: 商务女性生图.v2.md
123
+
124
+ - id: hero-image
125
+ mode: image
126
+ prompt: "为产品主页生成一张极简横幅图"
127
+ system_prompt: "@prompts/image-system.txt"
128
+ count: 3
129
+ output: hero.jpg
130
+
131
+ - id: transcript
132
+ mode: audio
133
+ audio_file: meeting.mp3
134
+ prompt: "请输出标准 SRT 字幕"
135
+ ```
136
+ ```
137
+
138
+ ### YAML 字段说明
139
+
140
+ **顶层字段**(全局配置)
141
+
142
+ | 字段 | 必填 | 说明 | 可被 task 覆盖 |
143
+ |------|------|------|----------------|
144
+ | `mode` | ✓ | 默认任务类型:`chat` / `image` / `audio` | ✓ |
145
+ | `model` | | 全局模型名称,覆盖环境变量配置 | ✓ |
146
+ | `system_prompt` | | 全局 system prompt,支持 `@文件` | ✓ |
147
+ | `input` | | 全局输入文件(chat 模式),路径或路径数组 | ✓ |
148
+ | `reference` | | 全局参考图(chat/image 模式) | ✓ |
149
+ | `audio_file` | | 全局音频文件(audio 模式) | ✓ |
150
+ | `output_dir` | | 任务输出基目录,相对路径基于 YAML 所在目录 | - |
151
+ | `concurrency` | | 并发数 | - |
152
+ | `temperature` | | 温度参数(0.0-2.0) | ✓ |
153
+ | `max_output_tokens` | | 最大输出 token 数 | ✓ |
154
+ | `tasks` | ✓ | 任务数组,不能为空 | - |
155
+
156
+ **task 字段**(任务级配置)
157
+
158
+ | 字段 | 必填 | 适用模式 | 说明 |
159
+ |------|------|----------|------|
160
+ | `id` | | 全部 | 任务标识,默认 `task-序号` |
161
+ | `mode` | | 全部 | 覆盖顶层 `mode` |
162
+ | `model` | | 全部 | 覆盖顶层 `model` |
163
+ | `prompt` | | 全部 | 支持字面量或 `@文件` |
164
+ | `system_prompt` | | 全部 | 覆盖顶层 `system_prompt`,支持 `@文件` |
165
+ | `input` | | chat | 覆盖顶层 `input`,路径或路径数组 |
166
+ | `reference` | | chat / image | 覆盖顶层 `reference` |
167
+ | `audio_file` | | audio | 覆盖顶层 `audio_file` |
168
+ | `edit` | | chat | 目标文本文件,进入 diff 编辑模式 |
169
+ | `count` | | image | 图片生成数量,遵循 image 模式并发配置 |
170
+ | `output` | | 全部 | 输出路径,未指定时使用默认规则 |
171
+ | `temperature` | | 全部 | 覆盖顶层 `temperature` |
172
+ | `max_output_tokens` | | 全部 | 覆盖顶层 `max_output_tokens` |
173
+
174
+ ## 配置
175
+
176
+ 脚本从 `~/.config/llm-api/.env` 读取配置,环境变量可覆盖:
177
+
178
+ ```bash
179
+ API_KEY=your_api_key
180
+ BASE_URL=https://your-api-endpoint/v1
181
+ ```
182
+
183
+ 模型优先级:
184
+
185
+ | 模式 | 优先级 |
186
+ |------|--------|
187
+ | chat | `LLM_TEXT_MODEL` → `LLM_MODEL` → `OPENAI_CHAT_MODEL` → `OPENAI_MODEL` |
188
+ | image | `LLM_IMAGE_MODEL` → `LLM_MODEL` → `OPENAI_IMAGE_MODEL` |
189
+ | audio | `LLM_AUDIO_MODEL` → `LLM_MODEL` → `GEMINI_AUDIO_MODEL` |
190
+
191
+ 并发优先级:
192
+ - 顶层 YAML `concurrency`
193
+ - `LLM_CONCURRENCY`
194
+ - 兼容旧变量(仅单一任务类型 batch 时生效)
195
+ - 默认 `4`
196
+
197
+ ## 高级选项
198
+
199
+ 以下参数保留用于高级控制,不作为主示例默认写法:
200
+ - `-t, --temperature`
201
+ - `-m, --max-output-tokens`
202
+ - `--model`
203
+
204
+ ## 常见错误
205
+
206
+ | 问题 | 原因 | 正确方式 |
207
+ |------|------|----------|
208
+ | 把 YAML 文件直接传给 `chat` | 误以为会自动进入 batch | 使用 `batch tasks.yaml` |
209
+ | audio 命令把文本写成第二个位置参数 | audio 只接受音频文件位置参数 | 用 `-p "你的要求"` |
210
+ | image/chat 想从文件读取 prompt 但直接写路径 | 未使用 `@` 前缀 | 用 `@prompt.txt` |
211
+ | 想编辑文本文件却传给 `-r` | `-r` 仅支持图片参考 | 用 `--edit file.md` |
212
+ | batch 中 audio 任务写成 `audio` 字段 | 字段名不对 | 使用 `audio_file` |
213
+
214
+ ## --debug 位置自由
215
+
216
+ `--debug` 可以放在任意位置:
217
+
218
+ ```bash
219
+ llm --debug chat "test" # group 级别
220
+ llm chat --debug "test" # command 级别(中间)
221
+ llm chat "test" --debug # command 级别(末尾)
222
+ ```
223
+
224
+ ## 输出规则速查
225
+
226
+ | 模式 | 未指定输出时 |
227
+ |------|--------------|
228
+ | chat | 打印到终端 |
229
+ | image | `./gemini-output/output_时间戳.jpg` |
230
+ | audio | 输入音频同目录同名 `.srt` |
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env python3
2
+ """向后兼容垫片 — 转发到 llm_cli.cli.cli()。"""
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ # 确保 src 布局在未 pip install 时也能工作
7
+ _src = str(Path(__file__).resolve().parent / "src")
8
+ if _src not in sys.path:
9
+ sys.path.insert(0, _src)
10
+
11
+ from llm_cli.cli import cli
12
+
13
+ if __name__ == "__main__":
14
+ cli()
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "shellus-llmcmd"
7
+ dynamic = ["version"]
8
+ description = "Unified LLM CLI for text, image, audio, and batch tasks"
9
+ readme = "SKILL.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "click>=8.1",
13
+ "openai>=1.0",
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ batch = [
18
+ "pyyaml>=6.0",
19
+ ]
20
+
21
+ [project.scripts]
22
+ llm = "llm_cli.cli:cli"
23
+
24
+ [tool.hatch.version]
25
+ path = "src/llm_cli/__init__.py"
26
+
27
+ [tool.hatch.build.targets.wheel]
28
+ packages = ["src/llm_cli"]
@@ -0,0 +1 @@
1
+ __version__ = "1.0.0"
@@ -0,0 +1,3 @@
1
+ from .cli import cli
2
+
3
+ cli()
@@ -0,0 +1,85 @@
1
+ import json
2
+ import sys
3
+
4
+ DEBUG = False
5
+
6
+
7
+ def set_debug(value):
8
+ global DEBUG
9
+ DEBUG = bool(value)
10
+
11
+
12
+ def debug_log(*args, **kwargs):
13
+ if DEBUG:
14
+ print("[DEBUG]", *args, **kwargs, file=sys.stderr)
15
+
16
+
17
+ def api_call(client, model, messages, temperature=None, max_output_tokens=None):
18
+ kwargs = {
19
+ "model": model,
20
+ "messages": messages,
21
+ }
22
+ request_method = "POST"
23
+ request_url = str(client.base_url).rstrip("/") + "/chat/completions"
24
+ if temperature is not None:
25
+ kwargs["temperature"] = temperature
26
+ if max_output_tokens is not None:
27
+ kwargs["max_tokens"] = max_output_tokens
28
+
29
+ if DEBUG:
30
+ import copy
31
+ debug_kwargs = copy.deepcopy(kwargs)
32
+ # 截断 base64 数据避免刷屏
33
+ for msg in debug_kwargs.get("messages", []):
34
+ content = msg.get("content")
35
+ if isinstance(content, list):
36
+ for item in content:
37
+ if isinstance(item, dict):
38
+ for key in ("file", "input_audio", "image_url"):
39
+ if key in item:
40
+ sub = item[key]
41
+ if isinstance(sub, dict):
42
+ for field in ("file_data", "data", "url"):
43
+ if field in sub and len(str(sub[field])) > 100:
44
+ sub[field] = sub[field][:50] + f"...<truncated, total {len(sub[field])} chars>"
45
+ debug_log("请求方法:", request_method)
46
+ debug_log("请求 URL:", request_url)
47
+ debug_log("请求参数:", json.dumps(debug_kwargs, ensure_ascii=False, indent=2))
48
+
49
+ try:
50
+ response = client.chat.completions.create(**kwargs)
51
+ if DEBUG:
52
+ msg = response.choices[0].message
53
+ debug_log("响应 content:", repr((getattr(msg, "content", None) or "")[:500]))
54
+ debug_log("响应 images:", getattr(msg, "images", None))
55
+ return response
56
+ except json.JSONDecodeError:
57
+ # 上游返回 SSE 流式,fallback 到 stream=True
58
+ debug_log("JSONDecodeError,fallback 到 stream=True")
59
+ kwargs["stream"] = True
60
+ stream = client.chat.completions.create(**kwargs)
61
+
62
+ content_parts = []
63
+ for chunk in stream:
64
+ if chunk.choices and chunk.choices[0].delta.content:
65
+ content_parts.append(chunk.choices[0].delta.content)
66
+
67
+ full_content = "".join(content_parts)
68
+ if DEBUG:
69
+ debug_log("流式收集 content:", repr(full_content[:500]))
70
+
71
+ # 构造兼容响应对象
72
+ class FakeMessage:
73
+ def __init__(self, content):
74
+ self.content = content
75
+ self.images = None
76
+
77
+ class FakeChoice:
78
+ def __init__(self, message):
79
+ self.message = message
80
+
81
+ class FakeResponse:
82
+ def __init__(self, content):
83
+ self.choices = [FakeChoice(FakeMessage(content))]
84
+
85
+ return FakeResponse(full_content)