jinzd-ai-cli 0.4.33 → 0.4.35

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.
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.33";
9
+ var VERSION = "0.4.35";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -7,7 +7,7 @@ import {
7
7
  ProviderNotFoundError,
8
8
  RateLimitError,
9
9
  schemaToJsonSchema
10
- } from "./chunk-HI7WP73P.js";
10
+ } from "./chunk-PTNODLIV.js";
11
11
  import {
12
12
  APP_NAME,
13
13
  CONFIG_DIR_NAME,
@@ -20,7 +20,7 @@ import {
20
20
  MCP_TOOL_PREFIX,
21
21
  PLUGINS_DIR_NAME,
22
22
  VERSION
23
- } from "./chunk-BIMGUPLT.js";
23
+ } from "./chunk-NHANECAS.js";
24
24
 
25
25
  // src/config/config-manager.ts
26
26
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -2100,8 +2100,12 @@ var OllamaProvider = class extends OpenAICompatibleProvider {
2100
2100
  defaultBaseUrl = "http://localhost:11434/v1";
2101
2101
  defaultTimeout = 12e4;
2102
2102
  // 本地推理可能较慢,默认 2 分钟
2103
- /** 动态模型列表,initialize 时从 Ollama 获取 */
2103
+ /** 动态模型列表 */
2104
2104
  dynamicModels = [];
2105
+ /** 是否已成功连接过 Ollama */
2106
+ connected = false;
2107
+ /** 记住 base URL 用于后续重连 */
2108
+ ollamaHost = "http://localhost:11434";
2105
2109
  info = {
2106
2110
  id: "ollama",
2107
2111
  displayName: "Ollama (Local)",
@@ -2114,36 +2118,56 @@ var OllamaProvider = class extends OpenAICompatibleProvider {
2114
2118
  };
2115
2119
  async initialize(apiKey, options) {
2116
2120
  const baseUrl = options?.baseUrl ?? this.defaultBaseUrl;
2117
- const ollamaHost = baseUrl.replace(/\/v1\/?$/, "");
2118
- this.dynamicModels = await this.fetchModels(ollamaHost);
2119
- if (this.dynamicModels.length === 0) {
2120
- throw new Error("Ollama is running but no models are installed. Run `ollama pull <model>` to install one.");
2121
- }
2122
- const preferred = ["llama3.1", "llama3", "qwen2.5", "qwen2", "deepseek-r1", "mistral", "gemma2"];
2123
- const defaultModel = this.dynamicModels.find(
2124
- (m) => preferred.some((p) => m.id.startsWith(p))
2125
- )?.id ?? this.dynamicModels[0].id;
2126
- Object.assign(this.info, {
2127
- defaultModel,
2128
- baseUrl,
2129
- models: this.dynamicModels
2130
- });
2121
+ this.ollamaHost = baseUrl.replace(/\/v1\/?$/, "");
2131
2122
  await super.initialize(apiKey || "ollama", { ...options, baseUrl });
2123
+ await this.tryConnect();
2132
2124
  }
2133
2125
  /**
2134
- * Ollama /api/tags 获取本地模型列表。
2135
- * 如果 Ollama 未运行则抛出错误。
2126
+ * 尝试连接 Ollama 并刷新模型列表。
2127
+ * 成功返回 true,失败返回 false(不抛异常)。
2136
2128
  */
2137
- async fetchModels(ollamaHost) {
2138
- const url = `${ollamaHost}/api/tags`;
2139
- let response;
2129
+ async tryConnect() {
2140
2130
  try {
2141
- response = await fetch(url, { signal: AbortSignal.timeout(5e3) });
2131
+ this.dynamicModels = await this.fetchModels(this.ollamaHost);
2132
+ if (this.dynamicModels.length === 0) {
2133
+ this.connected = false;
2134
+ return false;
2135
+ }
2136
+ const preferred = ["llama3.1", "llama3", "qwen2.5", "qwen3", "qwen2", "deepseek-r1", "mistral", "gemma2"];
2137
+ const defaultModel = this.dynamicModels.find(
2138
+ (m) => preferred.some((p) => m.id.startsWith(p))
2139
+ )?.id ?? this.dynamicModels[0].id;
2140
+ Object.assign(this.info, {
2141
+ defaultModel,
2142
+ baseUrl: `${this.ollamaHost}/v1`,
2143
+ models: this.dynamicModels
2144
+ });
2145
+ this.connected = true;
2146
+ return true;
2142
2147
  } catch {
2143
- throw new Error(
2144
- `Cannot connect to Ollama at ${ollamaHost}. Make sure Ollama is running (https://ollama.com).`
2148
+ this.connected = false;
2149
+ return false;
2150
+ }
2151
+ }
2152
+ /**
2153
+ * 确保已连接。在 chat/chatStream 前调用,若未连接则尝试重连。
2154
+ */
2155
+ async ensureConnected() {
2156
+ if (this.connected && this.dynamicModels.length > 0) return;
2157
+ const ok = await this.tryConnect();
2158
+ if (!ok) {
2159
+ throw new ProviderError(
2160
+ "ollama",
2161
+ `Cannot connect to Ollama at ${this.ollamaHost}. Make sure Ollama is running (\`ollama serve\`) and has models installed (\`ollama pull <model>\`).`
2145
2162
  );
2146
2163
  }
2164
+ }
2165
+ /**
2166
+ * 从 Ollama /api/tags 获取本地模型列表。
2167
+ */
2168
+ async fetchModels(ollamaHost) {
2169
+ const url = `${ollamaHost}/api/tags`;
2170
+ const response = await fetch(url, { signal: AbortSignal.timeout(5e3) });
2147
2171
  if (!response.ok) {
2148
2172
  throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
2149
2173
  }
@@ -2163,32 +2187,27 @@ var OllamaProvider = class extends OpenAICompatibleProvider {
2163
2187
  };
2164
2188
  });
2165
2189
  }
2166
- /** 根据模型名估算上下文窗口(Ollama 模型元数据不含此信息) */
2190
+ /** 根据模型名估算上下文窗口 */
2167
2191
  estimateContextWindow(modelName) {
2168
2192
  const name = modelName.toLowerCase();
2169
2193
  if (name.includes("llama3") || name.includes("llama-3")) return 131072;
2170
2194
  if (name.includes("qwen2.5") || name.includes("qwen3")) return 131072;
2171
2195
  if (name.includes("qwen2") || name.includes("qwen-2")) return 32768;
2172
2196
  if (name.includes("deepseek")) return 65536;
2173
- if (name.includes("gemma2") || name.includes("gemma-2")) return 8192;
2197
+ if (name.includes("gemma2") || name.includes("gemma-2") || name.includes("gemma3")) return 8192;
2174
2198
  if (name.includes("mistral")) return 32768;
2175
2199
  if (name.includes("phi")) return 16384;
2176
2200
  if (name.includes("codellama") || name.includes("code-llama")) return 16384;
2177
2201
  return 8192;
2178
2202
  }
2179
- /** 动态返回当前 Ollama 安装的模型 */
2203
+ /** 实时刷新模型列表 */
2180
2204
  async listModels() {
2205
+ await this.tryConnect();
2181
2206
  return this.dynamicModels.length > 0 ? this.dynamicModels : this.info.models;
2182
2207
  }
2183
2208
  /** Ollama 无需验证 API Key,只检查服务是否可达 */
2184
2209
  async validateApiKey() {
2185
- try {
2186
- const ollamaHost = this.info.baseUrl.replace(/\/v1\/?$/, "");
2187
- await fetch(`${ollamaHost}/api/tags`, { signal: AbortSignal.timeout(5e3) });
2188
- return true;
2189
- } catch {
2190
- return false;
2191
- }
2210
+ return this.tryConnect();
2192
2211
  }
2193
2212
  };
2194
2213
 
@@ -8,7 +8,7 @@ import { platform } from "os";
8
8
  import chalk from "chalk";
9
9
 
10
10
  // src/core/constants.ts
11
- var VERSION = "0.4.33";
11
+ var VERSION = "0.4.35";
12
12
  var APP_NAME = "ai-cli";
13
13
  var CONFIG_DIR_NAME = ".aicli";
14
14
  var CONFIG_FILE_NAME = "config.json";
@@ -9,7 +9,7 @@ import {
9
9
  SUBAGENT_DEFAULT_MAX_ROUNDS,
10
10
  SUBAGENT_MAX_ROUNDS_LIMIT,
11
11
  runTestsTool
12
- } from "./chunk-BIMGUPLT.js";
12
+ } from "./chunk-NHANECAS.js";
13
13
 
14
14
  // src/tools/builtin/bash.ts
15
15
  import { execSync } from "child_process";
@@ -387,7 +387,7 @@ ${content}`);
387
387
  }
388
388
  }
389
389
  async function runTaskMode(config, providers, configManager, topic) {
390
- const { TaskOrchestrator } = await import("./task-orchestrator-PJCFJMMW.js");
390
+ const { TaskOrchestrator } = await import("./task-orchestrator-GPTGIDZ5.js");
391
391
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
392
392
  let interrupted = false;
393
393
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  saveDevState,
25
25
  sessionHasMeaningfulContent,
26
26
  setupProxy
27
- } from "./chunk-QUHABI3Q.js";
27
+ } from "./chunk-GSPLUO3M.js";
28
28
  import {
29
29
  ToolExecutor,
30
30
  ToolRegistry,
@@ -37,7 +37,7 @@ import {
37
37
  spawnAgentContext,
38
38
  theme,
39
39
  undoStack
40
- } from "./chunk-HI7WP73P.js";
40
+ } from "./chunk-PTNODLIV.js";
41
41
  import {
42
42
  fileCheckpoints
43
43
  } from "./chunk-4BKXL7SM.js";
@@ -61,7 +61,7 @@ import {
61
61
  SKILLS_DIR_NAME,
62
62
  VERSION,
63
63
  buildUserIdentityPrompt
64
- } from "./chunk-BIMGUPLT.js";
64
+ } from "./chunk-NHANECAS.js";
65
65
 
66
66
  // src/index.ts
67
67
  import { program } from "commander";
@@ -978,6 +978,16 @@ function createDefaultCommands() {
978
978
  ctx.renderer.renderError(`Provider '${targetId}' is not configured. Run: ai-cli config`);
979
979
  return;
980
980
  }
981
+ const targetProvider = ctx.providers.get(targetId);
982
+ if (targetProvider.info.models.length === 0) {
983
+ const models = await targetProvider.listModels();
984
+ if (models.length === 0) {
985
+ ctx.renderer.renderError(
986
+ `Provider '${targetId}' has no available models. ` + (targetId === "ollama" ? "Make sure Ollama is running (`ollama serve`) and has models (`ollama pull <model>`)." : "Check provider configuration.")
987
+ );
988
+ return;
989
+ }
990
+ }
981
991
  if (targetId === ctx.getCurrentProvider()) {
982
992
  ctx.renderer.printInfo(`Already using provider: ${targetId}`);
983
993
  return;
@@ -1003,8 +1013,9 @@ function createDefaultCommands() {
1003
1013
  let targetModel;
1004
1014
  if (!args[0]) {
1005
1015
  const provider = ctx.providers.get(ctx.getCurrentProvider());
1016
+ const models = await provider.listModels();
1006
1017
  const currentModel = ctx.getCurrentModel();
1007
- const items = provider.info.models.map((m) => ({
1018
+ const items = models.map((m) => ({
1008
1019
  value: m.id,
1009
1020
  label: m.id,
1010
1021
  hint: [
@@ -2087,7 +2098,7 @@ ${hint}` : "")
2087
2098
  usage: "/test [command|filter]",
2088
2099
  async execute(args, ctx) {
2089
2100
  try {
2090
- const { executeTests } = await import("./run-tests-MYDGL7IB.js");
2101
+ const { executeTests } = await import("./run-tests-IND26J25.js");
2091
2102
  const argStr = args.join(" ").trim();
2092
2103
  let testArgs = {};
2093
2104
  if (argStr) {
@@ -2737,8 +2748,11 @@ var PROVIDERS = [
2737
2748
  { value: "zhipu", name: "Zhipu (GLM)" },
2738
2749
  { value: "kimi", name: "Kimi (Moonshot AI)" },
2739
2750
  { value: "openai", name: "OpenAI" },
2740
- { value: "openrouter", name: "OpenRouter" }
2751
+ { value: "openrouter", name: "OpenRouter" },
2752
+ { value: "ollama", name: "Ollama (Local)" }
2741
2753
  ];
2754
+ var NO_KEY_PROVIDERS = /* @__PURE__ */ new Set(["ollama"]);
2755
+ var OLLAMA_DEFAULT_HOST = "http://localhost:11434";
2742
2756
  function maskKey(key) {
2743
2757
  if (key.length <= 10) return "****";
2744
2758
  return key.slice(0, 6) + "****" + key.slice(-4);
@@ -2798,6 +2812,9 @@ ${greeting} Starting ai-cli...
2798
2812
  await this.setupGoogleSearch();
2799
2813
  } else if (action === "apikey") {
2800
2814
  const choicesWithStatus = PROVIDERS.map((p) => {
2815
+ if (NO_KEY_PROVIDERS.has(p.value)) {
2816
+ return { value: p.value, name: `${p.name} ${theme.dim("[no API key needed]")}` };
2817
+ }
2801
2818
  const existingKey = this.config.getApiKey(p.value);
2802
2819
  const status = existingKey ? theme.success(`[${maskKey(existingKey)}]`) : theme.dim("[not configured]");
2803
2820
  return { value: p.value, name: `${p.name} ${status}` };
@@ -2822,6 +2839,10 @@ ${greeting} Starting ai-cli...
2822
2839
  }
2823
2840
  }
2824
2841
  async setupProvider(providerId) {
2842
+ if (NO_KEY_PROVIDERS.has(providerId)) {
2843
+ await this.setupOllama();
2844
+ return;
2845
+ }
2825
2846
  const provider = PROVIDERS.find((p) => p.value === providerId);
2826
2847
  const displayName = provider?.name ?? providerId;
2827
2848
  const existingKey = this.config.getApiKey(providerId);
@@ -2864,6 +2885,57 @@ Managing ${displayName} API Key`);
2864
2885
  console.log(theme.success(`API key saved for ${displayName}: ${maskKey(newKey)}
2865
2886
  `));
2866
2887
  }
2888
+ /** Ollama 专用配置:检测连通性、发现本地模型、可选自定义地址 */
2889
+ async setupOllama() {
2890
+ console.log(theme.heading("\n\u{1F999} Ollama (Local) Configuration"));
2891
+ console.log(theme.dim(" Ollama runs AI models locally \u2014 no API key needed."));
2892
+ console.log(theme.dim(" Install: https://ollama.com\n"));
2893
+ const customBaseUrls = this.config.get("customBaseUrls") ?? {};
2894
+ const currentHost = customBaseUrls["ollama"] ? customBaseUrls["ollama"].replace(/\/v1\/?$/, "") : OLLAMA_DEFAULT_HOST;
2895
+ const ollamaHost = await input({
2896
+ message: "Ollama server address:",
2897
+ default: currentHost,
2898
+ validate: (val) => {
2899
+ if (!val) return "Address cannot be empty";
2900
+ if (!val.startsWith("http://") && !val.startsWith("https://")) {
2901
+ return "Must start with http:// or https://";
2902
+ }
2903
+ return true;
2904
+ }
2905
+ });
2906
+ const host = ollamaHost.replace(/\/+$/, "");
2907
+ console.log(theme.dim(`
2908
+ Connecting to ${host}...`));
2909
+ let models = [];
2910
+ try {
2911
+ const res = await fetch(`${host}/api/tags`, { signal: AbortSignal.timeout(5e3) });
2912
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
2913
+ const data = await res.json();
2914
+ models = data.models ?? [];
2915
+ } catch {
2916
+ console.log(theme.error(` \u2717 Cannot connect to Ollama at ${host}`));
2917
+ console.log(theme.dim(" Make sure Ollama is running: ollama serve\n"));
2918
+ return;
2919
+ }
2920
+ if (models.length === 0) {
2921
+ console.log(theme.warning(" \u26A0 Ollama is running but no models installed."));
2922
+ console.log(theme.dim(" Install a model: ollama pull llama3.1\n"));
2923
+ return;
2924
+ }
2925
+ console.log(theme.success(` \u2713 Connected! Found ${models.length} model(s):
2926
+ `));
2927
+ for (const m of models) {
2928
+ const size = m.details?.parameter_size ?? `${(m.size / 1e9).toFixed(1)}GB`;
2929
+ console.log(` \u2022 ${m.name} ${theme.dim(`(${size})`)}`);
2930
+ }
2931
+ console.log();
2932
+ if (host !== OLLAMA_DEFAULT_HOST) {
2933
+ const urls = { ...customBaseUrls, ollama: `${host}/v1` };
2934
+ this.config.set("customBaseUrls", urls);
2935
+ }
2936
+ this.config.save();
2937
+ console.log(theme.success(" Ollama configured! Use /provider ollama to switch.\n"));
2938
+ }
2867
2939
  async setupProxy() {
2868
2940
  const current = this.config.get("proxy") ?? "";
2869
2941
  console.log("\nHTTP/HTTPS Proxy Configuration");
@@ -5397,7 +5469,7 @@ program.command("web").description("Start Web UI server with browser-based chat
5397
5469
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
5398
5470
  process.exit(1);
5399
5471
  }
5400
- const { startWebServer } = await import("./server-MHRSLD7P.js");
5472
+ const { startWebServer } = await import("./server-VPTEWZD4.js");
5401
5473
  await startWebServer({ port, host: options.host });
5402
5474
  });
5403
5475
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -5630,7 +5702,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
5630
5702
  }),
5631
5703
  config.get("customProviders")
5632
5704
  );
5633
- const { startHub } = await import("./hub-H4GR45XE.js");
5705
+ const { startHub } = await import("./hub-IUMZBNBH.js");
5634
5706
  await startHub(
5635
5707
  {
5636
5708
  topic: topic ?? "",
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-BIMGUPLT.js";
5
+ } from "./chunk-NHANECAS.js";
6
6
  export {
7
7
  executeTests,
8
8
  runTestsTool
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-PHKIBOYX.js";
4
+ } from "./chunk-B4FJ7JTH.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -15,7 +15,7 @@ import {
15
15
  hadPreviousWriteToolCalls,
16
16
  loadDevState,
17
17
  setupProxy
18
- } from "./chunk-QUHABI3Q.js";
18
+ } from "./chunk-GSPLUO3M.js";
19
19
  import {
20
20
  AuthManager
21
21
  } from "./chunk-BYNY5JPB.js";
@@ -33,7 +33,7 @@ import {
33
33
  spawnAgentContext,
34
34
  truncateOutput,
35
35
  undoStack
36
- } from "./chunk-HI7WP73P.js";
36
+ } from "./chunk-PTNODLIV.js";
37
37
  import "./chunk-4BKXL7SM.js";
38
38
  import {
39
39
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -52,7 +52,7 @@ import {
52
52
  SKILLS_DIR_NAME,
53
53
  VERSION,
54
54
  buildUserIdentityPrompt
55
- } from "./chunk-BIMGUPLT.js";
55
+ } from "./chunk-NHANECAS.js";
56
56
 
57
57
  // src/web/server.ts
58
58
  import express from "express";
@@ -1606,7 +1606,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1606
1606
  case "test": {
1607
1607
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
1608
1608
  try {
1609
- const { executeTests } = await import("./run-tests-MYDGL7IB.js");
1609
+ const { executeTests } = await import("./run-tests-IND26J25.js");
1610
1610
  const argStr = args.join(" ").trim();
1611
1611
  let testArgs = {};
1612
1612
  if (argStr) {
@@ -4,11 +4,11 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-HI7WP73P.js";
7
+ } from "./chunk-PTNODLIV.js";
8
8
  import "./chunk-4BKXL7SM.js";
9
9
  import {
10
10
  SUBAGENT_ALLOWED_TOOLS
11
- } from "./chunk-BIMGUPLT.js";
11
+ } from "./chunk-NHANECAS.js";
12
12
 
13
13
  // src/hub/task-orchestrator.ts
14
14
  import { createInterface } from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.33",
3
+ "version": "0.4.35",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",