@ducci/jarvis 1.0.68 → 1.0.69
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 +1 -1
- package/src/scripts/onboarding.js +34 -51
- package/src/server/tools.js +65 -0
package/package.json
CHANGED
|
@@ -390,16 +390,42 @@ async function run() {
|
|
|
390
390
|
console.log(chalk.green(`\nModel ${chalk.bold(selectedModel)} saved to settings.`));
|
|
391
391
|
|
|
392
392
|
// --- VISION MODEL STEP (OPTIONAL) ---
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
393
|
+
let skipVision = false;
|
|
394
|
+
if (settings.visionModel) {
|
|
395
|
+
const { visionAction } = await inquirer.prompt([
|
|
396
|
+
{
|
|
397
|
+
type: 'list',
|
|
398
|
+
name: 'visionAction',
|
|
399
|
+
message: `Vision model is configured (${chalk.yellow(settings.visionModel)}). What do you want to do?`,
|
|
400
|
+
choices: [
|
|
401
|
+
{ name: 'Keep current vision model', value: 'keep' },
|
|
402
|
+
{ name: 'Change vision model', value: 'change' },
|
|
403
|
+
{ name: 'Disable vision', value: 'disable' },
|
|
404
|
+
],
|
|
405
|
+
}
|
|
406
|
+
]);
|
|
407
|
+
if (visionAction === 'keep') {
|
|
408
|
+
skipVision = true;
|
|
409
|
+
} else if (visionAction === 'disable') {
|
|
410
|
+
delete settings.visionProvider;
|
|
411
|
+
delete settings.visionModel;
|
|
412
|
+
saveSettings(settings);
|
|
413
|
+
console.log(chalk.yellow('Vision model disabled.'));
|
|
414
|
+
skipVision = true;
|
|
399
415
|
}
|
|
400
|
-
|
|
416
|
+
} else {
|
|
417
|
+
const { configureVision } = await inquirer.prompt([
|
|
418
|
+
{
|
|
419
|
+
type: 'confirm',
|
|
420
|
+
name: 'configureVision',
|
|
421
|
+
message: 'Do you want to configure a separate vision model for image analysis (e.g. for Telegram photos)?',
|
|
422
|
+
default: false,
|
|
423
|
+
}
|
|
424
|
+
]);
|
|
425
|
+
if (!configureVision) skipVision = true;
|
|
426
|
+
}
|
|
401
427
|
|
|
402
|
-
if (
|
|
428
|
+
if (!skipVision) {
|
|
403
429
|
const { visionProvider } = await inquirer.prompt([
|
|
404
430
|
{
|
|
405
431
|
type: 'list',
|
|
@@ -558,11 +584,6 @@ async function run() {
|
|
|
558
584
|
settings.visionModel = visionModel;
|
|
559
585
|
saveSettings(settings);
|
|
560
586
|
console.log(chalk.green(`Vision model ${chalk.bold(visionModel)} saved.`));
|
|
561
|
-
} else {
|
|
562
|
-
// Clear vision config if user opts out
|
|
563
|
-
delete settings.visionProvider;
|
|
564
|
-
delete settings.visionModel;
|
|
565
|
-
saveSettings(settings);
|
|
566
587
|
}
|
|
567
588
|
|
|
568
589
|
// --- TELEGRAM CHANNEL STEP (OPTIONAL) ---
|
|
@@ -640,44 +661,6 @@ async function run() {
|
|
|
640
661
|
}
|
|
641
662
|
}
|
|
642
663
|
|
|
643
|
-
// --- PERPLEXITY STEP (OPTIONAL) ---
|
|
644
|
-
const existingPerplexityKey = loadEnvVar('PERPLEXITY_API_KEY');
|
|
645
|
-
const { configurePerplexity } = await inquirer.prompt([
|
|
646
|
-
{
|
|
647
|
-
type: 'confirm',
|
|
648
|
-
name: 'configurePerplexity',
|
|
649
|
-
message: 'Do you want to configure Perplexity web search?',
|
|
650
|
-
default: !!existingPerplexityKey
|
|
651
|
-
}
|
|
652
|
-
]);
|
|
653
|
-
|
|
654
|
-
if (configurePerplexity) {
|
|
655
|
-
let keepPerplexityKey = false;
|
|
656
|
-
if (existingPerplexityKey) {
|
|
657
|
-
const { keep } = await inquirer.prompt([
|
|
658
|
-
{
|
|
659
|
-
type: 'confirm',
|
|
660
|
-
name: 'keep',
|
|
661
|
-
message: 'A PERPLEXITY_API_KEY is already configured. Do you want to keep it?',
|
|
662
|
-
default: true
|
|
663
|
-
}
|
|
664
|
-
]);
|
|
665
|
-
keepPerplexityKey = keep;
|
|
666
|
-
}
|
|
667
|
-
if (!keepPerplexityKey) {
|
|
668
|
-
const { perplexityKey } = await inquirer.prompt([
|
|
669
|
-
{
|
|
670
|
-
type: 'password',
|
|
671
|
-
name: 'perplexityKey',
|
|
672
|
-
message: 'Enter your Perplexity API key (from perplexity.ai/settings/api):',
|
|
673
|
-
validate: (input) => input.trim().length > 0 || 'API key cannot be empty.'
|
|
674
|
-
}
|
|
675
|
-
]);
|
|
676
|
-
saveEnvVar('PERPLEXITY_API_KEY', perplexityKey.trim());
|
|
677
|
-
console.log(chalk.green('Perplexity API key saved.'));
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
664
|
// --- PM2 + LOG ROTATION STEP ---
|
|
682
665
|
const pm2Check = spawnSync('pm2', ['--version'], { stdio: 'pipe' });
|
|
683
666
|
if (pm2Check.status !== 0) {
|
package/src/server/tools.js
CHANGED
|
@@ -656,6 +656,71 @@ const SEED_TOOLS = {
|
|
|
656
656
|
return { status: 'ok', name: args.name, content };
|
|
657
657
|
`,
|
|
658
658
|
},
|
|
659
|
+
analyze_image: {
|
|
660
|
+
definition: {
|
|
661
|
+
type: 'function',
|
|
662
|
+
function: {
|
|
663
|
+
name: 'analyze_image',
|
|
664
|
+
description: 'Fetch an image from a URL and analyze it using the configured vision model. Returns a detailed description of the image. Use this whenever a user shares an image URL and asks about its content.',
|
|
665
|
+
parameters: {
|
|
666
|
+
type: 'object',
|
|
667
|
+
properties: {
|
|
668
|
+
url: {
|
|
669
|
+
type: 'string',
|
|
670
|
+
description: 'The URL of the image to analyze (http or https).',
|
|
671
|
+
},
|
|
672
|
+
prompt: {
|
|
673
|
+
type: 'string',
|
|
674
|
+
description: 'Optional question or instruction for the vision model, e.g. "What text is visible?" or "Describe the chart". Defaults to a general description.',
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
required: ['url'],
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
code: `
|
|
682
|
+
const settingsPath = path.join(process.env.HOME, '.jarvis/data/config/settings.json');
|
|
683
|
+
const settings = JSON.parse(await fs.promises.readFile(settingsPath, 'utf8').catch(() => '{}'));
|
|
684
|
+
const visionModel = settings.visionModel;
|
|
685
|
+
const visionProvider = settings.visionProvider;
|
|
686
|
+
if (!visionModel || !visionProvider) {
|
|
687
|
+
return { status: 'error', message: 'No vision model configured. Set visionModel and visionProvider in settings.' };
|
|
688
|
+
}
|
|
689
|
+
let apiKey, baseURL;
|
|
690
|
+
if (visionProvider === 'z-ai') {
|
|
691
|
+
apiKey = process.env.ZAI_API_KEY;
|
|
692
|
+
baseURL = 'https://api.z.ai/api/coding/paas/v4/';
|
|
693
|
+
} else {
|
|
694
|
+
apiKey = process.env.OPENROUTER_API_KEY;
|
|
695
|
+
baseURL = 'https://openrouter.ai/api/v1';
|
|
696
|
+
}
|
|
697
|
+
if (!apiKey) return { status: 'error', message: 'No API key found for vision provider: ' + visionProvider };
|
|
698
|
+
const imgResponse = await fetch(args.url);
|
|
699
|
+
if (!imgResponse.ok) return { status: 'error', message: 'Failed to fetch image: HTTP ' + imgResponse.status };
|
|
700
|
+
const buffer = await imgResponse.arrayBuffer();
|
|
701
|
+
const base64 = Buffer.from(buffer).toString('base64');
|
|
702
|
+
const contentType = imgResponse.headers.get('content-type') || 'image/jpeg';
|
|
703
|
+
const dataUrl = 'data:' + contentType + ';base64,' + base64;
|
|
704
|
+
const textPrompt = args.prompt?.trim()
|
|
705
|
+
? 'The user shared this image with the following question/context: "' + args.prompt.trim() + '"\\n\\nPlease describe what you see, paying special attention to anything relevant to their message.'
|
|
706
|
+
: 'Please describe this image in detail. Include all visible text, objects, colors, layout, and any other relevant details.';
|
|
707
|
+
const apiResponse = await fetch(baseURL + (baseURL.endsWith('/') ? '' : '/') + 'chat/completions', {
|
|
708
|
+
method: 'POST',
|
|
709
|
+
headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json' },
|
|
710
|
+
body: JSON.stringify({
|
|
711
|
+
model: visionModel,
|
|
712
|
+
messages: [{ role: 'user', content: [
|
|
713
|
+
{ type: 'image_url', image_url: { url: dataUrl } },
|
|
714
|
+
{ type: 'text', text: textPrompt },
|
|
715
|
+
]}],
|
|
716
|
+
}),
|
|
717
|
+
});
|
|
718
|
+
const result = await apiResponse.json();
|
|
719
|
+
if (!apiResponse.ok) return { status: 'error', message: result.error?.message || 'Vision API error' };
|
|
720
|
+
const description = result.choices?.[0]?.message?.content?.trim() || '(no description returned)';
|
|
721
|
+
return { status: 'ok', description };
|
|
722
|
+
`,
|
|
723
|
+
},
|
|
659
724
|
};
|
|
660
725
|
|
|
661
726
|
export function seedTools() {
|