zapmyco 0.20.6 → 0.22.1

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.
package/README.md CHANGED
@@ -140,8 +140,7 @@ ai-typescript-starter/
140
140
  | `deno task fmt:check` | 格式检查 |
141
141
  | `deno task check` | 类型检查 |
142
142
  | `deno task check:all` | 完整检查 (fmt + lint + check + test) |
143
- | `deno task cli` | 运行 CLI |
144
- | `deno task ai` | AI 对话模式 |
143
+ | `deno task cli` | 运行 CLI(交互模式/一次性任务) |
145
144
  | `deno task release` | 创建发布 |
146
145
  | `deno task release:dry` | 发布干运行 (不实际发布) |
147
146
  | `deno task build:npm` | dnt 构建 npm 包 |
package/esm/deno.js CHANGED
@@ -4,16 +4,17 @@ export default {
4
4
  "docs/"
5
5
  ],
6
6
  "name": "@zapmyco/zapmyco",
7
- "version": "0.20.6",
7
+ "version": "0.22.1",
8
8
  "description": "基于 Deno 的 AI 驱动命令行工具",
9
9
  "exports": "./src/index.ts",
10
10
  "tasks": {
11
- "cli": "deno run --allow-env --allow-net src/index.ts",
12
- "ai": "deno run --allow-env --allow-net src/index.ts ai",
13
- "install:global": "deno install -g -n zapmyco --allow-env --allow-net src/index.ts",
14
- "dev": "deno run --watch src/index.ts",
15
- "test": "deno test --allow-env",
16
- "test:coverage": "deno test --allow-env --coverage && deno coverage",
11
+ "cli": "deno run --allow-env --allow-net --allow-read=$HOME/.zapmyco src/cli.ts",
12
+ "init": "deno run --allow-env --allow-read --allow-write src/cli.ts init",
13
+ "settings": "deno run --allow-env --allow-read=$HOME/.zapmyco src/cli.ts settings",
14
+ "install:global": "deno install -g -n zapmyco --allow-env --allow-net --allow-read --allow-write src/cli.ts",
15
+ "dev": "deno run --watch src/cli.ts",
16
+ "test": "deno test --allow-env --allow-read --allow-write",
17
+ "test:coverage": "deno test --allow-env --allow-read --allow-write --coverage && deno coverage",
17
18
  "lint": "deno lint",
18
19
  "fmt": "deno fmt",
19
20
  "fmt:check": "deno fmt --check",
@@ -55,6 +56,7 @@ export default {
55
56
  "CHANGELOG.md",
56
57
  "AGENTS.md",
57
58
  "src/**/*_test.ts",
59
+ "src/cli.ts",
58
60
  "tools",
59
61
  "dist"
60
62
  ]
@@ -4,6 +4,8 @@
4
4
  import * as dntShim from "../_dnt.shims.js";
5
5
  import Anthropic from '@anthropic-ai/sdk';
6
6
  import { TextLineStream } from './text-line-stream.js';
7
+ import { loadSettings, resolveEnvRef } from './settings.js';
8
+ import { getModelInfo } from './models.js';
7
9
  const DEFAULT_BASE_URL = 'https://api.deepseek.com/anthropic';
8
10
  const DEFAULT_MODEL = 'deepseek-v4-flash';
9
11
  const DEFAULT_SYSTEM_PROMPT = '你是一个 AI 编程助手,帮助用户解决编程问题。';
@@ -24,6 +26,12 @@ export class AiAgent {
24
26
  writable: true,
25
27
  value: void 0
26
28
  });
29
+ Object.defineProperty(this, "maxTokens", {
30
+ enumerable: true,
31
+ configurable: true,
32
+ writable: true,
33
+ value: void 0
34
+ });
27
35
  Object.defineProperty(this, "messages", {
28
36
  enumerable: true,
29
37
  configurable: true,
@@ -36,34 +44,65 @@ export class AiAgent {
36
44
  writable: true,
37
45
  value: void 0
38
46
  });
39
- const apiKey = options.apiKey ?? dntShim.Deno.env.get('DEEPSEEK_API_KEY');
47
+ // 加载 ~/.zapmyco/settings.json(文件不存在时静默降级)
48
+ const settings = loadSettings();
49
+ const llm = settings?.llm;
50
+ // 1. 确定模型配置档名称
51
+ const profileName = options.modelProfile ?? 'default';
52
+ // 2. 从配置档解析模型名称
53
+ const profileModelName = llm?.models?.[profileName];
54
+ // 3. 最终模型名称:options.model > 配置档模型名 > 默认值
55
+ const modelName = options.model ?? profileModelName ?? DEFAULT_MODEL;
56
+ // 4. 从内置注册表查找模型信息
57
+ const modelInfo = getModelInfo(modelName);
58
+ // 5. 确定供应商名称:options.provider > 注册表中的供应商 > 'default'
59
+ const providerName = options.provider ?? modelInfo?.provider ?? 'default';
60
+ // 6. 解析 apiKey:options > settings.providers[provider].apiKey > 环境变量
61
+ let apiKey;
62
+ if (options.apiKey) {
63
+ apiKey = options.apiKey;
64
+ }
65
+ else if (providerName) {
66
+ const providerCfg = llm?.providers?.[providerName];
67
+ if (providerCfg?.apiKey) {
68
+ apiKey = resolveEnvRef(providerCfg.apiKey);
69
+ }
70
+ }
40
71
  if (!apiKey) {
41
- throw new Error('DEEPSEEK_API_KEY 未设置。请通过环境变量 DEEPSEEK_API_KEY 设置 API Key。');
72
+ apiKey = dntShim.Deno.env.get('DEEPSEEK_API_KEY');
42
73
  }
43
- this.client = new Anthropic({
44
- baseURL: options.baseURL ?? DEFAULT_BASE_URL,
45
- apiKey,
46
- });
47
- this.model = options.model ?? DEFAULT_MODEL;
74
+ if (!apiKey) {
75
+ throw new Error('DEEPSEEK_API_KEY 未设置。请运行 `zapmyco init` 或设置环境变量 DEEPSEEK_API_KEY。');
76
+ }
77
+ // 7. 确定 baseURL:options > 注册表中的 baseURL > 默认值
78
+ const baseURL = options.baseURL ?? modelInfo?.baseURL ?? DEFAULT_BASE_URL;
79
+ // 8. 确定 maxTokens:options > 注册表中的 maxOutputTokens > 默认值 4096
80
+ this.maxTokens = options.maxTokens ?? modelInfo?.maxOutputTokens ?? 4096;
81
+ this.client = new Anthropic({ baseURL, apiKey });
82
+ this.model = modelName;
48
83
  this.systemPrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
49
84
  }
50
85
  /**
51
- * 非流式对话 - 发送消息并获取完整回复
86
+ * 非流式对话(内部使用流式 API) - 发送消息并获取完整回复
52
87
  * @param input - 用户输入
53
88
  * @returns 完整回复文本
54
89
  */
55
90
  async chat(input) {
56
91
  this.messages.push({ role: 'user', content: input });
57
- const response = await this.client.messages.create({
92
+ // 内部使用流式 API 以避免非流式请求被拒绝或超时
93
+ let fullContent = '';
94
+ const stream = this.client.messages.stream({
58
95
  model: this.model,
59
- max_tokens: 4096,
96
+ max_tokens: this.maxTokens,
60
97
  system: this.systemPrompt,
61
98
  messages: this.messages,
62
99
  });
63
- const firstBlock = response.content[0];
64
- const content = firstBlock?.type === 'text' ? firstBlock.text : '';
65
- this.messages.push({ role: 'assistant', content });
66
- return content;
100
+ stream.on('text', (text) => {
101
+ fullContent += text;
102
+ });
103
+ await stream.finalMessage();
104
+ this.messages.push({ role: 'assistant', content: fullContent });
105
+ return fullContent;
67
106
  }
68
107
  /**
69
108
  * 流式对话 - 发送消息并通过回调逐块获取回复
@@ -75,7 +114,7 @@ export class AiAgent {
75
114
  this.messages.push({ role: 'user', content: input });
76
115
  const stream = this.client.messages.stream({
77
116
  model: this.model,
78
- max_tokens: 4096,
117
+ max_tokens: this.maxTokens,
79
118
  system: this.systemPrompt,
80
119
  messages: this.messages,
81
120
  });
package/esm/src/cli.js ADDED
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI 入口 — 基于 Commander.js 的命令行界面
4
+ */
5
+ import "../_dnt.polyfills.js";
6
+ import * as dntShim from "../_dnt.shims.js";
7
+ import { Command, CommanderError } from 'commander';
8
+ import { createConfig, greet, VERSION } from './index.js';
9
+ import { AiAgent } from './ai-agent.js';
10
+ function getSettingsPath() {
11
+ return `${dntShim.Deno.env.get('HOME') ?? '.'}/.zapmyco/settings.json`;
12
+ }
13
+ function getSettingsDir() {
14
+ return `${dntShim.Deno.env.get('HOME') ?? '.'}/.zapmyco`;
15
+ }
16
+ async function promptUser(question) {
17
+ const encoder = new TextEncoder();
18
+ dntShim.Deno.stdout.writeSync(encoder.encode(question));
19
+ const buf = new Uint8Array(1024);
20
+ const n = await dntShim.Deno.stdin.read(buf);
21
+ if (n === null)
22
+ return '';
23
+ return new TextDecoder().decode(buf.subarray(0, n)).trim();
24
+ }
25
+ async function handleInitCommand() {
26
+ const filePath = getSettingsPath();
27
+ const dir = getSettingsDir();
28
+ // 检查是否已存在
29
+ try {
30
+ dntShim.Deno.statSync(filePath);
31
+ return {
32
+ exitCode: 1,
33
+ stdout: '',
34
+ stderr: `${filePath} 已存在。如需重新初始化,请先删除该文件。`,
35
+ };
36
+ }
37
+ catch (err) {
38
+ if (!(err instanceof dntShim.Deno.errors.NotFound)) {
39
+ return { exitCode: 1, stdout: '', stderr: String(err) };
40
+ }
41
+ }
42
+ // 交互式询问 API Key
43
+ const apiKey = await promptUser('? DeepSeek API Key(输入密钥,或直接回车使用环境变量 DEEPSEEK_API_KEY): ');
44
+ // 写入配置文件(新结构)
45
+ const settings = {
46
+ llm: {
47
+ providers: {
48
+ deepseek: {
49
+ apiKey: apiKey || '${env.DEEPSEEK_API_KEY}',
50
+ },
51
+ },
52
+ models: {
53
+ advanced: 'deepseek-reasoner',
54
+ default: 'deepseek-v4-flash',
55
+ light: 'deepseek-v4-flash',
56
+ },
57
+ },
58
+ };
59
+ try {
60
+ dntShim.Deno.mkdirSync(dir, { recursive: true });
61
+ dntShim.Deno.writeTextFileSync(filePath, JSON.stringify(settings, null, 2) + '\n');
62
+ }
63
+ catch (error) {
64
+ return { exitCode: 1, stdout: '', stderr: String(error) };
65
+ }
66
+ return {
67
+ exitCode: 0,
68
+ stdout: `已创建 ${filePath}\n请运行 \`zapmyco settings\` 查看配置。`,
69
+ stderr: '',
70
+ };
71
+ }
72
+ function maskApiKey(value) {
73
+ const envRef = value.match(/^\$\{env\.(.+)\}$/);
74
+ if (envRef) {
75
+ return `\${env.${envRef[1]}}`;
76
+ }
77
+ if (value.length <= 8) {
78
+ return value.slice(0, 3) + '***';
79
+ }
80
+ return value.slice(0, 3) + '***' + value.slice(-4);
81
+ }
82
+ function displaySettings(fileContent) {
83
+ // 脱敏 apiKey(支持新版和旧版结构)
84
+ if (fileContent.llm && typeof fileContent.llm === 'object') {
85
+ const llm = fileContent.llm;
86
+ // 新版: llm.providers.<name>.apiKey
87
+ if (llm.providers && typeof llm.providers === 'object') {
88
+ const providers = llm.providers;
89
+ for (const cfg of Object.values(providers)) {
90
+ if (typeof cfg.apiKey === 'string') {
91
+ cfg.apiKey = maskApiKey(cfg.apiKey);
92
+ }
93
+ }
94
+ }
95
+ // 旧版: llm.apiKey
96
+ if (typeof llm.apiKey === 'string') {
97
+ llm.apiKey = maskApiKey(llm.apiKey);
98
+ }
99
+ }
100
+ return JSON.stringify(fileContent, null, 2);
101
+ }
102
+ function readSettingsFile(filePath) {
103
+ try {
104
+ const content = dntShim.Deno.readTextFileSync(filePath);
105
+ return JSON.parse(content);
106
+ }
107
+ catch (error) {
108
+ if (error instanceof dntShim.Deno.errors.NotFound) {
109
+ throw new Error(`${filePath} 不存在。请运行 \`zapmyco init\` 创建。`);
110
+ }
111
+ if (error instanceof Error && error.name === 'NotCapable') {
112
+ throw new Error(`权限不足: ${filePath}\n请使用 --allow-read 权限运行。`);
113
+ }
114
+ if (error instanceof SyntaxError) {
115
+ throw new Error(`${filePath} JSON 格式错误。`);
116
+ }
117
+ throw error;
118
+ }
119
+ }
120
+ /**
121
+ * CLI 入口 - 解析参数并执行对应操作
122
+ * @param args - 命令行参数数组
123
+ * @returns CLI 执行结果
124
+ */
125
+ export async function cli(args) {
126
+ let capturedStdout = '';
127
+ let capturedStderr = '';
128
+ const program = new Command();
129
+ program.exitOverride();
130
+ program.configureOutput({
131
+ writeOut: (str) => {
132
+ capturedStdout += str;
133
+ },
134
+ writeErr: (str) => {
135
+ capturedStderr += str;
136
+ },
137
+ });
138
+ program.name('zapmyco');
139
+ program.version(`v${VERSION}`, '-v, --version');
140
+ program.description('基于 Deno 的 AI 驱动命令行工具');
141
+ program.helpOption('-h, --help', '显示帮助信息');
142
+ // --- greet ---
143
+ program.command('greet')
144
+ .description('向指定名称打招呼')
145
+ .argument('<name>', '要打招呼的名称')
146
+ .action((name) => {
147
+ try {
148
+ capturedStdout += greet(name);
149
+ }
150
+ catch (e) {
151
+ const msg = e instanceof Error ? e.message : String(e);
152
+ capturedStderr += msg;
153
+ throw new CommanderError(1, 'commander.greetError', msg);
154
+ }
155
+ });
156
+ // --- config ---
157
+ program.command('config')
158
+ .description('显示配置信息')
159
+ .action(() => {
160
+ const config = createConfig();
161
+ capturedStdout += JSON.stringify(config, null, 2);
162
+ });
163
+ // --- init ---
164
+ program.command('init')
165
+ .description('初始化 LLM 配置')
166
+ .action(async () => {
167
+ const result = await handleInitCommand();
168
+ capturedStdout += result.stdout;
169
+ capturedStderr += result.stderr;
170
+ if (result.exitCode !== 0) {
171
+ throw new CommanderError(result.exitCode, 'commander.initError', capturedStderr);
172
+ }
173
+ });
174
+ // --- settings ---
175
+ program.command('settings')
176
+ .description('显示 LLM 配置')
177
+ .argument('[subcommand]', '子命令: path')
178
+ .action((subcommand) => {
179
+ if (subcommand === 'path') {
180
+ capturedStdout += getSettingsPath();
181
+ return;
182
+ }
183
+ if (subcommand && subcommand !== 'show') {
184
+ capturedStderr += `未知子命令: ${subcommand}\n可用命令: settings, settings path`;
185
+ throw new CommanderError(1, 'commander.settingsError', capturedStderr);
186
+ }
187
+ // settings / settings show
188
+ const filePath = getSettingsPath();
189
+ try {
190
+ const fileContent = readSettingsFile(filePath);
191
+ capturedStdout += displaySettings(fileContent);
192
+ }
193
+ catch (error) {
194
+ const msg = error instanceof Error ? error.message : String(error);
195
+ capturedStderr += msg;
196
+ throw new CommanderError(1, 'commander.settingsError', msg);
197
+ }
198
+ });
199
+ // --- run ---
200
+ program.command('run')
201
+ .description('一次性执行 AI 任务,完成后退出')
202
+ .option('--profile <name>', '指定模型配置档')
203
+ .argument('<content...>', '任务描述')
204
+ .action(async (contentArgs, options) => {
205
+ const settingsPath = getSettingsPath();
206
+ try {
207
+ dntShim.Deno.statSync(settingsPath);
208
+ }
209
+ catch {
210
+ capturedStderr +=
211
+ `未找到配置文件 ${settingsPath}\n请先运行 \`zapmyco init\` 初始化 LLM 配置。`;
212
+ throw new CommanderError(1, 'commander.runError', capturedStderr);
213
+ }
214
+ try {
215
+ const agent = new AiAgent({ modelProfile: options.profile });
216
+ const task = contentArgs.join(' ');
217
+ const encoder = new TextEncoder();
218
+ await agent.chatStream(task, (chunk) => {
219
+ dntShim.Deno.stdout.writeSync(encoder.encode(chunk));
220
+ });
221
+ dntShim.Deno.stdout.writeSync(encoder.encode('\n'));
222
+ }
223
+ catch (error) {
224
+ const message = error instanceof Error ? error.message : String(error);
225
+ capturedStderr += message;
226
+ throw new CommanderError(1, 'commander.runError', capturedStderr);
227
+ }
228
+ });
229
+ // --- 执行 ---
230
+ try {
231
+ if (args.length === 0) {
232
+ // 无参数时直接进入交互模式
233
+ const settingsPath = getSettingsPath();
234
+ try {
235
+ dntShim.Deno.statSync(settingsPath);
236
+ }
237
+ catch {
238
+ capturedStderr +=
239
+ `未找到配置文件 ${settingsPath}\n请先运行 \`zapmyco init\` 初始化 LLM 配置。`;
240
+ return { exitCode: 1, stdout: '', stderr: capturedStderr };
241
+ }
242
+ try {
243
+ const agent = new AiAgent();
244
+ await agent.startInteractiveChat();
245
+ }
246
+ catch (error) {
247
+ const message = error instanceof Error ? error.message : String(error);
248
+ capturedStderr += message;
249
+ return { exitCode: 1, stdout: '', stderr: capturedStderr };
250
+ }
251
+ return { exitCode: 0, stdout: '', stderr: '' };
252
+ }
253
+ await program.parseAsync(args, { from: 'user' });
254
+ return { exitCode: 0, stdout: capturedStdout, stderr: capturedStderr };
255
+ }
256
+ catch (err) {
257
+ if (err instanceof CommanderError) {
258
+ // exitCode === 0 时(如 --help、--version),忽略 commander 内部消息
259
+ if (err.exitCode === 0) {
260
+ return { exitCode: 0, stdout: capturedStdout, stderr: '' };
261
+ }
262
+ return {
263
+ exitCode: err.exitCode,
264
+ stdout: capturedStdout,
265
+ stderr: capturedStderr || err.message,
266
+ };
267
+ }
268
+ throw err;
269
+ }
270
+ }
271
+ if (globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).main) {
272
+ const encoder = new TextEncoder();
273
+ const result = await cli(dntShim.Deno.args);
274
+ if (result.stderr) {
275
+ dntShim.Deno.stderr.writeSync(encoder.encode(result.stderr + '\n'));
276
+ }
277
+ if (result.stdout) {
278
+ dntShim.Deno.stdout.writeSync(encoder.encode(result.stdout + '\n'));
279
+ }
280
+ dntShim.Deno.exit(result.exitCode);
281
+ }
package/esm/src/index.js CHANGED
@@ -3,9 +3,7 @@
3
3
  * 专为 AI 辅助开发时代打造
4
4
  */
5
5
  import "../_dnt.polyfills.js";
6
- import * as dntShim from "../_dnt.shims.js";
7
6
  import denoJson from '../deno.js';
8
- import { AiAgent } from './ai-agent.js';
9
7
  /** 当前库的版本号 */
10
8
  export const VERSION = denoJson.version;
11
9
  /**
@@ -43,68 +41,6 @@ export function createConfig(options) {
43
41
  createdAt: new Date(),
44
42
  };
45
43
  }
46
- /**
47
- * CLI 入口 - 解析参数并执行对应操作
48
- * @param args - 命令行参数数组
49
- * @returns CLI 执行结果
50
- */
51
- export async function cli(args) {
52
- const [command, ...rest] = args;
53
- if (command === 'greet') {
54
- const name = rest[0];
55
- if (!name) {
56
- return { exitCode: 1, stdout: '', stderr: '请指定名称' };
57
- }
58
- return { exitCode: 0, stdout: greet(name), stderr: '' };
59
- }
60
- if (command === 'config') {
61
- return {
62
- exitCode: 0,
63
- stdout: JSON.stringify(createConfig(), null, 2),
64
- stderr: '',
65
- };
66
- }
67
- if (command === 'ai') {
68
- try {
69
- const agent = new AiAgent();
70
- await agent.startInteractiveChat();
71
- return { exitCode: 0, stdout: '', stderr: '' };
72
- }
73
- catch (error) {
74
- const message = error instanceof Error ? error.message : String(error);
75
- return { exitCode: 1, stdout: '', stderr: message };
76
- }
77
- }
78
- if (command === '--version' || command === '-v' || command === '-V') {
79
- return { exitCode: 0, stdout: `v${VERSION}`, stderr: '' };
80
- }
81
- const helpText = [
82
- `ZapMyCo v${VERSION}`,
83
- '',
84
- '用法:',
85
- ' greet <name> 向指定名称打招呼',
86
- ' config 显示配置信息',
87
- ' ai 进入 AI 对话模式',
88
- ' --version, -v, -V 显示版本号',
89
- ' --help, -h 显示帮助信息',
90
- ].join('\n');
91
- if (!command || command === '--help' || command === '-h') {
92
- return { exitCode: 0, stdout: helpText, stderr: '' };
93
- }
94
- return {
95
- exitCode: 1,
96
- stdout: '',
97
- stderr: `未知命令: ${command}\n${helpText}`,
98
- };
99
- }
100
- if (globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta).main) {
101
- const result = await cli(dntShim.Deno.args);
102
- if (result.stderr)
103
- console.error(result.stderr);
104
- if (result.stdout)
105
- console.log(result.stdout);
106
- dntShim.Deno.exit(result.exitCode);
107
- }
108
44
  // 默认导出
109
45
  export default {
110
46
  greet,
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Models - 内置模型注册表
3
+ *
4
+ * 集中维护所有内置模型的元信息(供应商归属、baseURL、能力)。
5
+ * settings.json 中只需引用模型名称,详细信息由此处提供。
6
+ */
7
+ /** 内置模型注册表 */
8
+ const BUILT_IN_MODELS = {
9
+ 'deepseek-v4-flash': {
10
+ provider: 'deepseek',
11
+ baseURL: 'https://api.deepseek.com/anthropic',
12
+ capabilities: ['text'],
13
+ contextWindow: 1_000_000,
14
+ maxOutputTokens: 384_000,
15
+ },
16
+ 'deepseek-v4-pro': {
17
+ provider: 'deepseek',
18
+ baseURL: 'https://api.deepseek.com/anthropic',
19
+ capabilities: ['text'],
20
+ contextWindow: 1_000_000,
21
+ maxOutputTokens: 384_000,
22
+ },
23
+ 'deepseek-reasoner': {
24
+ provider: 'deepseek',
25
+ baseURL: 'https://api.deepseek.com/anthropic',
26
+ capabilities: ['text'],
27
+ contextWindow: 128_000,
28
+ maxOutputTokens: 16_384,
29
+ },
30
+ 'glm-4-flash': {
31
+ provider: 'glm',
32
+ baseURL: 'https://open.bigmodel.cn/api/anthropic',
33
+ capabilities: ['text'],
34
+ contextWindow: 128_000,
35
+ maxOutputTokens: 16_384,
36
+ },
37
+ 'glm-4v': {
38
+ provider: 'glm',
39
+ baseURL: 'https://open.bigmodel.cn/api/anthropic',
40
+ capabilities: ['text', 'vision'],
41
+ contextWindow: 128_000,
42
+ maxOutputTokens: 16_384,
43
+ },
44
+ 'glm-5v-turbo': {
45
+ provider: 'glm',
46
+ baseURL: 'https://open.bigmodel.cn/api/anthropic',
47
+ capabilities: ['text', 'vision'],
48
+ contextWindow: 200_000,
49
+ maxOutputTokens: 128_000,
50
+ },
51
+ 'glm-5.1': {
52
+ provider: 'glm',
53
+ baseURL: 'https://open.bigmodel.cn/api/anthropic',
54
+ capabilities: ['text'],
55
+ contextWindow: 200_000,
56
+ maxOutputTokens: 128_000,
57
+ },
58
+ };
59
+ /**
60
+ * 根据模型名称获取内置模型信息
61
+ * @param name - 模型名称
62
+ * @returns 模型信息,未找到时返回 undefined
63
+ */
64
+ export function getModelInfo(name) {
65
+ return BUILT_IN_MODELS[name];
66
+ }
67
+ /**
68
+ * 获取所有内置模型名称列表
69
+ */
70
+ export function getBuiltInModelNames() {
71
+ return Object.keys(BUILT_IN_MODELS);
72
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Settings - ~/.zapmyco/settings.json 配置管理
3
+ */
4
+ import * as dntShim from "../_dnt.shims.js";
5
+ const SETTINGS_PATH = '.zapmyco/settings.json';
6
+ /**
7
+ * 将旧版格式转换为新版格式
8
+ * 旧版: { llm: { apiKey, baseURL, model } }
9
+ * 新版: { llm: { providers: { default: { apiKey } }, models: { default: model } } }
10
+ * 只提取字符串类型的值
11
+ */
12
+ function convertLegacySettings(legacy) {
13
+ return {
14
+ providers: {
15
+ default: {
16
+ apiKey: typeof legacy.apiKey === 'string' ? legacy.apiKey : undefined,
17
+ },
18
+ },
19
+ models: {
20
+ default: typeof legacy.model === 'string' ? legacy.model : 'deepseek-v4-flash',
21
+ },
22
+ };
23
+ }
24
+ /**
25
+ * 检测是否为旧版 LLM 配置格式
26
+ * 要求 apiKey 或 model 为字符串类型
27
+ */
28
+ function isLegacyFormat(llm) {
29
+ if (typeof llm !== 'object' || llm === null)
30
+ return false;
31
+ return typeof llm.apiKey === 'string' ||
32
+ typeof llm.model === 'string';
33
+ }
34
+ /**
35
+ * 解析 ${env.VAR} 引用
36
+ * - "${env.DEEPSEEK_API_KEY}" → 从环境变量 DEEPSEEK_API_KEY 读取
37
+ * - "sk-xxx" → 原样返回
38
+ */
39
+ export function resolveEnvRef(value) {
40
+ const match = value.match(/^\$\{env\.(.+)\}$/);
41
+ if (!match)
42
+ return value;
43
+ const envVar = match[1];
44
+ const resolved = dntShim.Deno.env.get(envVar);
45
+ if (!resolved) {
46
+ throw new Error(`环境变量 ${envVar} 未设置。请在 ${SETTINGS_PATH} 中配置或设置环境变量 ${envVar}。`);
47
+ }
48
+ return resolved;
49
+ }
50
+ /**
51
+ * 加载 ~/.zapmyco/settings.json
52
+ * 文件不存在时返回 null,不报错
53
+ * 自动兼容旧版格式
54
+ */
55
+ export function loadSettings() {
56
+ const home = dntShim.Deno.env.get('HOME');
57
+ if (!home)
58
+ return null;
59
+ const filePath = `${home}/${SETTINGS_PATH}`;
60
+ try {
61
+ const content = dntShim.Deno.readTextFileSync(filePath);
62
+ const parsed = JSON.parse(content);
63
+ const llmRaw = parsed?.llm;
64
+ if (!llmRaw || typeof llmRaw !== 'object')
65
+ return {};
66
+ // 兼容旧版格式
67
+ if (isLegacyFormat(llmRaw)) {
68
+ return { llm: convertLegacySettings(llmRaw) };
69
+ }
70
+ // 新版格式
71
+ const llm = llmRaw;
72
+ const providers = {};
73
+ if (llm.providers && typeof llm.providers === 'object') {
74
+ for (const [name, cfg] of Object.entries(llm.providers)) {
75
+ if (typeof cfg === 'object' && cfg !== null) {
76
+ const pc = cfg;
77
+ providers[name] = {
78
+ apiKey: typeof pc.apiKey === 'string' ? pc.apiKey : undefined,
79
+ };
80
+ }
81
+ }
82
+ }
83
+ const models = {};
84
+ if (llm.models && typeof llm.models === 'object') {
85
+ for (const [name, modelName] of Object.entries(llm.models)) {
86
+ if (typeof modelName === 'string') {
87
+ models[name] = modelName;
88
+ }
89
+ }
90
+ }
91
+ return {
92
+ llm: {
93
+ providers: Object.keys(providers).length > 0 ? providers : undefined,
94
+ models: Object.keys(models).length > 0 ? models : undefined,
95
+ },
96
+ };
97
+ }
98
+ catch (error) {
99
+ // 文件不存在
100
+ if (error instanceof dntShim.Deno.errors.NotFound)
101
+ return null;
102
+ // Deno 权限拒绝(NotCapable)
103
+ if (error instanceof Error && error.name === 'NotCapable')
104
+ return null;
105
+ // JSON 解析错误
106
+ if (error instanceof SyntaxError) {
107
+ throw new Error(`${filePath} JSON 格式错误: ${error.message}`);
108
+ }
109
+ throw error;
110
+ }
111
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapmyco",
3
- "version": "0.20.6",
3
+ "version": "0.22.1",
4
4
  "description": "AI 原生的 TypeScript 启动模板,专为 AI 辅助开发时代打造",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,10 +18,16 @@
18
18
  "types": "./types/src/index.d.ts",
19
19
  "default": "./esm/src/index.js"
20
20
  }
21
+ },
22
+ "./cli": {
23
+ "import": {
24
+ "types": "./types/src/cli.d.ts",
25
+ "default": "./esm/src/cli.js"
26
+ }
21
27
  }
22
28
  },
23
29
  "bin": {
24
- "zapmyco": "./esm/src/index.js"
30
+ "zapmyco": "./esm/src/cli.js"
25
31
  },
26
32
  "publishConfig": {
27
33
  "provenance": true,
@@ -29,6 +35,7 @@
29
35
  },
30
36
  "dependencies": {
31
37
  "@anthropic-ai/sdk": "0.39",
38
+ "commander": "14",
32
39
  "@deno/shim-deno": "~0.18.0"
33
40
  },
34
41
  "devDependencies": {
package/types/deno.d.ts CHANGED
@@ -6,7 +6,8 @@ declare namespace _default {
6
6
  let exports: string;
7
7
  let tasks: {
8
8
  cli: string;
9
- ai: string;
9
+ init: string;
10
+ settings: string;
10
11
  "install:global": string;
11
12
  dev: string;
12
13
  test: string;
@@ -1,11 +1,20 @@
1
1
  /** AiAgent 配置选项 */
2
2
  export interface AiAgentOptions {
3
- /** API Key,默认从 DEEPSEEK_API_KEY 环境变量读取 */
3
+ /** API Key,默认从 settings.json 或 DEEPSEEK_API_KEY 环境变量读取 */
4
4
  apiKey?: string;
5
- /** API 基础 URL,默认 https://api.deepseek.com/anthropic */
5
+ /** API 基础 URL,默认从内置模型注册表读取 */
6
6
  baseURL?: string;
7
- /** 模型名称,默认 deepseek-v4-flash */
7
+ /** 模型名称,默认从 modelProfile 或内置模型注册表读取 */
8
8
  model?: string;
9
+ /**
10
+ * 模型配置档名称(对应 settings.json llm.models 中的 key)
11
+ * 如 "default"、"advanced"、"light"、"vision"
12
+ */
13
+ modelProfile?: string;
14
+ /** 供应商名称(对应 settings.json llm.providers 中的 key) */
15
+ provider?: string;
16
+ /** 最大输出 tokens,默认从内置模型注册表读取 */
17
+ maxTokens?: number;
9
18
  /** 系统提示词 */
10
19
  systemPrompt?: string;
11
20
  }
@@ -20,11 +29,12 @@ export interface Message {
20
29
  export declare class AiAgent {
21
30
  private client;
22
31
  private model;
32
+ private maxTokens;
23
33
  private messages;
24
34
  private systemPrompt;
25
35
  constructor(options?: AiAgentOptions);
26
36
  /**
27
- * 非流式对话 - 发送消息并获取完整回复
37
+ * 非流式对话(内部使用流式 API) - 发送消息并获取完整回复
28
38
  * @param input - 用户输入
29
39
  * @returns 完整回复文本
30
40
  */
@@ -1 +1 @@
1
- {"version":3,"file":"ai-agent.d.ts","sourceRoot":"","sources":["../../src/src/ai-agent.ts"],"names":[],"mappings":"AASA,mBAAmB;AACnB,MAAM,WAAW,cAAc;IAC7B,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,WAAW;AACX,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD;;GAEG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,YAAY,CAAS;gBAEjB,OAAO,GAAE,cAAmB;IAgBxC;;;;OAIG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB1C;;;;;OAKG;IACG,UAAU,CACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAC9B,OAAO,CAAC,MAAM,CAAC;IAsBlB;;OAEG;IACG,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IA0C3C,cAAc;IACd,YAAY,IAAI,IAAI;IAIpB,eAAe;IACf,WAAW,IAAI,SAAS,OAAO,EAAE;CAGlC"}
1
+ {"version":3,"file":"ai-agent.d.ts","sourceRoot":"","sources":["../../src/src/ai-agent.ts"],"names":[],"mappings":"AAWA,mBAAmB;AACnB,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,WAAW;AACX,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD;;GAEG;AACH,qBAAa,OAAO;IAClB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,YAAY,CAAS;gBAEjB,OAAO,GAAE,cAAmB;IAkDxC;;;;OAIG;IACG,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsB1C;;;;;OAKG;IACG,UAAU,CACd,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAC9B,OAAO,CAAC,MAAM,CAAC;IAsBlB;;OAEG;IACG,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IA0C3C,cAAc;IACd,YAAY,IAAI,IAAI;IAIpB,eAAe;IACf,WAAW,IAAI,SAAS,OAAO,EAAE;CAGlC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * CLI 入口 — 基于 Commander.js 的命令行界面
3
+ */
4
+ import "../_dnt.polyfills.js";
5
+ /**
6
+ * CLI 执行结果
7
+ */
8
+ export interface CliResult {
9
+ /** 退出码 */
10
+ exitCode: number;
11
+ /** 标准输出 */
12
+ stdout: string;
13
+ /** 错误输出 */
14
+ stderr: string;
15
+ }
16
+ /**
17
+ * CLI 入口 - 解析参数并执行对应操作
18
+ * @param args - 命令行参数数组
19
+ * @returns CLI 执行结果
20
+ */
21
+ export declare function cli(args: string[]): Promise<CliResult>;
22
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/src/cli.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,sBAAsB,CAAC;AAS9B;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,UAAU;IACV,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW;IACX,MAAM,EAAE,MAAM,CAAC;IACf,WAAW;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AA6HD;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAsJ5D"}
@@ -48,23 +48,6 @@ export declare function greet(name: string): string;
48
48
  * ```
49
49
  */
50
50
  export declare function createConfig(options?: ConfigOptions): Config;
51
- /**
52
- * CLI 执行结果
53
- */
54
- export interface CliResult {
55
- /** 退出码 */
56
- exitCode: number;
57
- /** 标准输出 */
58
- stdout: string;
59
- /** 错误输出 */
60
- stderr: string;
61
- }
62
- /**
63
- * CLI 入口 - 解析参数并执行对应操作
64
- * @param args - 命令行参数数组
65
- * @returns CLI 执行结果
66
- */
67
- export declare function cli(args: string[]): Promise<CliResult>;
68
51
  declare const _default: {
69
52
  greet: typeof greet;
70
53
  createConfig: typeof createConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,sBAAsB,CAAC;AAQ9B,cAAc;AACd,eAAO,MAAM,OAAO,EAAE,MAAyB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,eAAe;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW;IACX,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAChD;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,eAAe;IACf,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,WAAW;IACX,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACvD,WAAW;IACX,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAO5D;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,UAAU;IACV,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW;IACX,MAAM,EAAE,MAAM,CAAC;IACf,WAAW;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAsD5D;;;;;;AAUD,wBAIE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,sBAAsB,CAAC;AAK9B,cAAc;AACd,eAAO,MAAM,OAAO,EAAE,MAAyB,CAAC;AAEhD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,eAAe;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW;IACX,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAChD;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,eAAe;IACf,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,WAAW;IACX,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACvD,WAAW;IACX,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAO5D;;;;;;AAGD,wBAIE"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Models - 内置模型注册表
3
+ *
4
+ * 集中维护所有内置模型的元信息(供应商归属、baseURL、能力)。
5
+ * settings.json 中只需引用模型名称,详细信息由此处提供。
6
+ */
7
+ /** 模型能力 */
8
+ export type ModelCapability = 'text' | 'vision';
9
+ /** 内置模型信息 */
10
+ export interface BuiltInModel {
11
+ /** 所属供应商标识(对应 settings.json 中 llm.providers 的 key) */
12
+ provider: string;
13
+ /** API 基础地址 */
14
+ baseURL: string;
15
+ /** 模型能力列表 */
16
+ capabilities: ModelCapability[];
17
+ /** 上下文窗口大小(tokens) */
18
+ contextWindow?: number;
19
+ /** 最大输出 tokens */
20
+ maxOutputTokens?: number;
21
+ }
22
+ /**
23
+ * 根据模型名称获取内置模型信息
24
+ * @param name - 模型名称
25
+ * @returns 模型信息,未找到时返回 undefined
26
+ */
27
+ export declare function getModelInfo(name: string): BuiltInModel | undefined;
28
+ /**
29
+ * 获取所有内置模型名称列表
30
+ */
31
+ export declare function getBuiltInModelNames(): string[];
32
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/src/models.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,WAAW;AACX,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEhD,aAAa;AACb,MAAM,WAAW,YAAY;IAC3B,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa;IACb,YAAY,EAAE,eAAe,EAAE,CAAC;IAChC,sBAAsB;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAuDD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEnE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAE/C"}
@@ -0,0 +1,32 @@
1
+ /** 供应商配置 */
2
+ export interface ProviderConfig {
3
+ /** API 密钥,支持 ${env.VAR} 语法 */
4
+ apiKey?: string;
5
+ }
6
+ /** LLM 配置(新格式) */
7
+ export interface LlmSettings {
8
+ /** 供应商字典,key 为唯一标识名(如 "deepseek"、"glm") */
9
+ providers?: Record<string, ProviderConfig>;
10
+ /**
11
+ * 模型配置档字典,key 为配置档名称(如 "default"、"advanced"、"light"、"vision"),
12
+ * value 为模型名称(对应内置模型注册表中的名称)
13
+ */
14
+ models?: Record<string, string>;
15
+ }
16
+ /** 顶层配置 */
17
+ export interface Settings {
18
+ llm?: LlmSettings;
19
+ }
20
+ /**
21
+ * 解析 ${env.VAR} 引用
22
+ * - "${env.DEEPSEEK_API_KEY}" → 从环境变量 DEEPSEEK_API_KEY 读取
23
+ * - "sk-xxx" → 原样返回
24
+ */
25
+ export declare function resolveEnvRef(value: string): string;
26
+ /**
27
+ * 加载 ~/.zapmyco/settings.json
28
+ * 文件不存在时返回 null,不报错
29
+ * 自动兼容旧版格式
30
+ */
31
+ export declare function loadSettings(): Settings | null;
32
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/src/settings.ts"],"names":[],"mappings":"AAQA,YAAY;AACZ,MAAM,WAAW,cAAc;IAC7B,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,kBAAkB;AAClB,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC3C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AASD,WAAW;AACX,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,EAAE,WAAW,CAAC;CACnB;AA+BD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYnD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,QAAQ,GAAG,IAAI,CA6D9C"}