@ducci/jarvis 1.0.58 → 1.0.60

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ducci/jarvis",
3
- "version": "1.0.58",
3
+ "version": "1.0.60",
4
4
  "description": "A fully automated agent system that lives on a server.",
5
5
  "main": "./src/index.js",
6
6
  "type": "module",
@@ -89,6 +89,21 @@ async function fetchAnthropicModels(apiKey) {
89
89
  }
90
90
  }
91
91
 
92
+ async function fetchZaiModels(apiKey) {
93
+ console.log(chalk.blue('Fetching models from Z.AI...'));
94
+ try {
95
+ const response = await fetch('https://api.z.ai/api/paas/v4/models', {
96
+ headers: { 'Authorization': `Bearer ${apiKey}` }
97
+ });
98
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
99
+ const data = await response.json();
100
+ return data.data || [];
101
+ } catch (error) {
102
+ console.error(chalk.red('Failed to fetch Z.AI models:'), error.message);
103
+ return [];
104
+ }
105
+ }
106
+
92
107
  async function run() {
93
108
  ensureDirectories();
94
109
 
@@ -105,6 +120,7 @@ async function run() {
105
120
  choices: [
106
121
  { name: 'OpenRouter (access many models via one key)', value: 'openrouter' },
107
122
  { name: 'Anthropic Direct (use your Anthropic API key)', value: 'anthropic' },
123
+ { name: 'Z.AI Direct (GLM models, use your Z.AI API key)', value: 'z-ai' },
108
124
  ],
109
125
  default: settings.provider || 'openrouter',
110
126
  }
@@ -142,6 +158,35 @@ async function run() {
142
158
  saveEnvVar('ANTHROPIC_API_KEY', apiKey);
143
159
  console.log(chalk.green('Anthropic API key saved.'));
144
160
  }
161
+ } else if (provider === 'z-ai') {
162
+ const existingKey = loadEnvVar('ZAI_API_KEY');
163
+ apiKey = existingKey;
164
+
165
+ if (existingKey) {
166
+ const { keepKey } = await inquirer.prompt([
167
+ {
168
+ type: 'confirm',
169
+ name: 'keepKey',
170
+ message: 'A ZAI_API_KEY is already configured. Do you want to keep it?',
171
+ default: true,
172
+ }
173
+ ]);
174
+ if (!keepKey) apiKey = null;
175
+ }
176
+
177
+ if (!apiKey) {
178
+ const { newKey } = await inquirer.prompt([
179
+ {
180
+ type: 'password',
181
+ name: 'newKey',
182
+ message: 'Enter your Z.AI API key (from open.bigmodel.cn):',
183
+ validate: (input) => input.length >= 10 || 'API key must be at least 10 characters long.',
184
+ }
185
+ ]);
186
+ apiKey = newKey;
187
+ saveEnvVar('ZAI_API_KEY', apiKey);
188
+ console.log(chalk.green('Z.AI API key saved.'));
189
+ }
145
190
  } else {
146
191
  const existingKey = loadEnvVar('OPENROUTER_API_KEY');
147
192
  apiKey = existingKey;
@@ -193,7 +238,41 @@ async function run() {
193
238
  }
194
239
 
195
240
  if (!selectedModel) {
196
- if (provider === 'anthropic') {
241
+ if (provider === 'z-ai') {
242
+ const models = await fetchZaiModels(apiKey);
243
+ let choices;
244
+ if (models.length > 0) {
245
+ choices = models.map(m => ({ name: m.id, value: m.id }));
246
+ } else {
247
+ console.log(chalk.yellow('Falling back to manual entry due to fetch failure.'));
248
+ choices = [];
249
+ }
250
+ choices.push({ name: 'Enter model ID manually', value: '__manual__' });
251
+
252
+ const { browsedModel } = await inquirer.prompt([
253
+ {
254
+ type: 'list',
255
+ name: 'browsedModel',
256
+ message: 'Select a Z.AI model:',
257
+ choices,
258
+ pageSize: 20,
259
+ }
260
+ ]);
261
+
262
+ if (browsedModel === '__manual__') {
263
+ const { manualModel } = await inquirer.prompt([
264
+ {
265
+ type: 'input',
266
+ name: 'manualModel',
267
+ message: 'Enter Z.AI model ID (e.g., glm-5):',
268
+ validate: (input) => input.trim().length > 0 || 'Model ID cannot be empty.',
269
+ }
270
+ ]);
271
+ selectedModel = manualModel.trim();
272
+ } else {
273
+ selectedModel = browsedModel;
274
+ }
275
+ } else if (provider === 'anthropic') {
197
276
  console.log(chalk.blue('Fetching available Claude models...'));
198
277
  const models = await fetchAnthropicModels(apiKey);
199
278
  const choices = models.map(m => ({
@@ -293,7 +372,9 @@ async function run() {
293
372
  settings.selectedModel = selectedModel;
294
373
  // Reset fallback to provider-appropriate default when switching providers or on first run
295
374
  if (!settings.fallbackModel || previousProvider !== provider) {
296
- settings.fallbackModel = provider === 'anthropic' ? 'claude-haiku-4-5-20251001' : 'openrouter/free';
375
+ if (provider === 'anthropic') settings.fallbackModel = 'claude-haiku-4-5-20251001';
376
+ else if (provider === 'z-ai') settings.fallbackModel = 'glm-4-flash';
377
+ else settings.fallbackModel = 'openrouter/free';
297
378
  }
298
379
  if (settings.maxIterations === undefined) {
299
380
  settings.maxIterations = 10;
@@ -46,6 +46,9 @@ export function loadConfig() {
46
46
  if (provider === 'anthropic') {
47
47
  apiKey = process.env.ANTHROPIC_API_KEY;
48
48
  if (!apiKey) throw new Error('ANTHROPIC_API_KEY not found. Run `jarvis setup` first.');
49
+ } else if (provider === 'z-ai') {
50
+ apiKey = process.env.ZAI_API_KEY;
51
+ if (!apiKey) throw new Error('ZAI_API_KEY not found. Add it to ~/.jarvis/.env first.');
49
52
  } else {
50
53
  apiKey = process.env.OPENROUTER_API_KEY;
51
54
  if (!apiKey) throw new Error('OPENROUTER_API_KEY not found. Run `jarvis setup` first.');
@@ -149,6 +149,12 @@ export function createClient(config) {
149
149
  if (config.provider === 'anthropic') {
150
150
  return createAnthropicClient(config.apiKey);
151
151
  }
152
+ if (config.provider === 'z-ai') {
153
+ return new OpenAI({
154
+ baseURL: 'https://api.z.ai/api/paas/v4/',
155
+ apiKey: config.apiKey,
156
+ });
157
+ }
152
158
  // Default: OpenRouter (OpenAI-compatible)
153
159
  return new OpenAI({
154
160
  baseURL: 'https://openrouter.ai/api/v1',
@@ -548,7 +548,7 @@ const SEED_TOOLS = {
548
548
  const logFile = path.join(logDir, String(chatId) + '-' + prefix + '.log');
549
549
  const ts = new Date().toISOString();
550
550
  await fs.promises.mkdir(logDir, { recursive: true });
551
- await fs.promises.appendFile(logFile, ts + ' [CRON] ' + String(args.message).replace(/\n/g, ' ') + '\n', 'utf8');
551
+ await fs.promises.appendFile(logFile, ts + ' [CRON] ' + String(args.message).replace(/\\n/g, ' ') + '\\n', 'utf8');
552
552
  } catch {}
553
553
  return { status: 'ok', chatId };
554
554
  `,