🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

agentlux

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

agentlux - npm Package Compare versions

Comparing version
2.1.0
to
3.0.0
+160
-17
index.js

@@ -229,2 +229,64 @@ const fs = require('fs').promises;

// ============================================================
// Aesthetic Reference Library
// ============================================================
const AESTHETIC_REFERENCE = {
compositionalRules: {
rule_of_thirds: 'Subject or key elements at intersection points of a 3x3 grid',
golden_ratio: 'Spiral or rectangular composition following 1:1.618 proportions',
dynamic_symmetry: 'Diagonal-based composition creating visual tension and movement',
leading_lines: 'Lines within the frame guiding the eye toward the subject',
negative_space: 'Intentional empty areas giving the subject room to breathe',
frame_within_frame: 'Natural elements forming a secondary frame around the subject',
subject_isolation: 'Clear separation between subject and background through placement or contrast'
},
tonalMoods: {
high_key: 'Predominantly bright tones, airy and optimistic',
low_key: 'Predominantly dark tones, dramatic and moody',
high_contrast: 'Strong difference between lights and darks, bold and graphic',
soft_tonal: 'Gentle gradations, subtle and contemplative',
split_lighting: 'Half light / half shadow, dramatic character revelation',
golden_hour: 'Warm directional light with long shadows, nostalgic warmth',
overcast_diffuse: 'Even soft lighting without harsh shadows, understated calm'
},
genreFrameworks: {
portrait: {
key_concerns: 'Eye contact, expression, skin tone fidelity, background separation',
crop_guidance: 'Preserve headroom; avoid cutting at joints; maintain eye-level framing',
effects_bias: 'Favor clean rendering; grain only for editorial/fashion mood'
},
landscape: {
key_concerns: 'Horizon placement, depth layers (foreground/mid/background), sky drama',
crop_guidance: 'Respect the horizon line; rule of thirds for sky/ground division',
effects_bias: 'Subtle vignette to frame; grain rarely appropriate'
},
street: {
key_concerns: 'Decisive moment, gesture, context, layering, spontaneity',
crop_guidance: 'Tighten to emphasize geometry and tension; embrace edge activity',
effects_bias: 'Grain and contrast serve the raw documentary feel'
},
architecture: {
key_concerns: 'Verticals, symmetry, geometric patterns, light on surfaces',
crop_guidance: 'Preserve symmetry axes; respect structural geometry',
effects_bias: 'Clean rendering preferred; vignette can emphasize central forms'
},
still_life: {
key_concerns: 'Arrangement, texture, light quality, color harmony',
crop_guidance: 'Tight framing to emphasize texture; negative space if intentional',
effects_bias: 'Depends on mood — commercial wants clean, art wants character'
}
}
};
function buildAestheticPromptSection() {
const rules = Object.entries(AESTHETIC_REFERENCE.compositionalRules)
.map(([k, v]) => ` - ${k.replace(/_/g, ' ')}: ${v}`).join('\n');
const moods = Object.entries(AESTHETIC_REFERENCE.tonalMoods)
.map(([k, v]) => ` - ${k.replace(/_/g, ' ')}: ${v}`).join('\n');
const genres = Object.entries(AESTHETIC_REFERENCE.genreFrameworks)
.map(([k, g]) => ` - ${k}: ${g.key_concerns}. Crop: ${g.crop_guidance}. Effects: ${g.effects_bias}`).join('\n');
return `Compositional patterns to evaluate:\n${rules}\n\nTonal moods to consider:\n${moods}\n\nGenre frameworks:\n${genres}`;
}
// ============================================================
// VLM Provider Abstraction

@@ -429,2 +491,6 @@ // ============================================================

const VALID_COMPOSITION_QUALITY = new Set(['excellent', 'good', 'fair', 'poor']);
const VALID_PROCESSING_INTENSITY = new Set(['minimal', 'moderate', 'full']);
const VALID_VIGNETTE_INTENSITY = new Set(['none', 'subtle', 'standard', 'dramatic']);
function parseCuratorResponse(raw) {

@@ -440,3 +506,10 @@ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {

colorRationale: typeof raw.color_rationale === 'string' ? raw.color_rationale : '',
lensRationale: typeof raw.lens_rationale === 'string' ? raw.lens_rationale : ''
lensRationale: typeof raw.lens_rationale === 'string' ? raw.lens_rationale : '',
compositionQuality: typeof raw.composition_quality === 'string' && VALID_COMPOSITION_QUALITY.has(raw.composition_quality) ? raw.composition_quality : 'fair',
cropRecommended: typeof raw.crop_recommended === 'boolean' ? raw.crop_recommended : true,
cropRationale: typeof raw.crop_rationale === 'string' ? raw.crop_rationale : '',
processingIntensity: typeof raw.processing_intensity === 'string' && VALID_PROCESSING_INTENSITY.has(raw.processing_intensity) ? raw.processing_intensity : 'full',
grainAppropriate: typeof raw.grain_appropriate === 'boolean' ? raw.grain_appropriate : null,
grainRationale: typeof raw.grain_rationale === 'string' ? raw.grain_rationale : '',
vignetteIntensity: typeof raw.vignette_intensity === 'string' && VALID_VIGNETTE_INTENSITY.has(raw.vignette_intensity) ? raw.vignette_intensity : 'standard'
};

@@ -454,5 +527,6 @@ }

const lensList = Object.entries(LENS_PROFILES).map(([k, l]) => ` - "${k}": ${l.name}`).join('\n');
const langInstruction = lang !== 'en' ? `\n\nIMPORTANT: Write ALL text values (master_rationale, color_rationale, lens_rationale) in ${lang}. Keep JSON keys and selection keys in English.` : '';
const aestheticSection = buildAestheticPromptSection();
const langInstruction = lang !== 'en' ? `\n\nIMPORTANT: Write ALL text values (master_rationale, color_rationale, lens_rationale, crop_rationale, grain_rationale) in ${lang}. Keep JSON keys and selection keys in English.` : '';
const prompt = `You are the Chief Curator of a world-class Leica photography exhibition. You have spent decades studying the masters who defined 35mm street and documentary photography.
const prompt = `You are the Chief Curator of a world-class Leica photography exhibition. You have spent decades studying the masters who defined 35mm street and documentary photography. You exercise restraint — a great curator knows when NOT to intervene.

@@ -464,3 +538,21 @@ Analyze this photograph (${width}x${height} pixels). Consider:

4. Color palette, contrast characteristics, and mood
5. Whether the existing composition is already strong — if so, respect it
${aestheticSection}
COMPOSITION ASSESSMENT:
Evaluate the image's existing composition. Rate composition_quality:
- "excellent": Strong intentional composition (clear use of recognized patterns, balanced framing, purposeful subject placement). Do NOT recommend cropping.
- "good": Solid composition with minor improvement opportunities. Cropping may refine but is not critical.
- "fair": Composition has potential but needs reframing to reach its best form.
- "poor": Weak or accidental framing that would greatly benefit from recomposition.
Set crop_recommended to false ONLY when composition_quality is "excellent" and the original framing should be preserved entirely.
EFFECTS ASSESSMENT:
Evaluate what processing this image genuinely needs:
- processing_intensity: "minimal" (color grade only), "moderate" (color + selective effects), or "full" (the complete treatment)
- grain_appropriate: true/false — would film grain genuinely enhance this image's mood, or would it feel like a filter slapped on?
- vignette_intensity: "none", "subtle", "standard", or "dramatic" — how much vignette serves this specific image?
Select the ONE master photographer whose compositional philosophy best matches this image:

@@ -476,3 +568,3 @@ ${masterList}

Return ONLY a JSON object:
{"master": "key", "master_rationale": "brief why", "color_profile": "key", "color_rationale": "brief why", "lens": "key", "lens_rationale": "brief why"}${langInstruction}`;
{"master": "key", "master_rationale": "brief why", "color_profile": "key", "color_rationale": "brief why", "lens": "key", "lens_rationale": "brief why", "composition_quality": "excellent|good|fair|poor", "crop_recommended": true/false, "crop_rationale": "brief why", "processing_intensity": "minimal|moderate|full", "grain_appropriate": true/false, "grain_rationale": "brief why", "vignette_intensity": "none|subtle|standard|dramatic"}${langInstruction}`;

@@ -483,7 +575,15 @@ const request = buildVLMRequest(provider, prompt, imageBase64);

async function masterCompose(imageBase64, width, height, masterKey, lang) {
async function masterCompose(imageBase64, width, height, masterKey, lang, curation) {
if (curation && curation.compositionQuality === 'excellent' && !curation.cropRecommended) {
return {
x: 0, y: 0, width, height,
rule: curation.cropRationale || 'Original composition preserved — already strong.'
};
}
const provider = resolveProvider(process.env.AGENTLUX_MASTER_MODEL);
const master = MASTER_REGISTRY[masterKey] || MASTER_REGISTRY.bresson;
const aestheticHint = `\n\nAesthetic context: ${buildAestheticPromptSection()}\n\nIf the composition is already strong, you may return coordinates covering the full frame (x:0, y:0, width:${width}, height:${height}) with a rule explaining why the original framing is optimal.`;
const langInstruction = lang !== 'en' ? `\n\nIMPORTANT: Write the "rule" value in ${lang}. Keep JSON keys, x, y, width, height as numbers.` : '';
const request = buildVLMRequest(provider, master.prompt(width, height) + langInstruction, imageBase64);
const request = buildVLMRequest(provider, master.prompt(width, height) + aestheticHint + langInstruction, imageBase64);
return parseCropBox(await callVLM(request));

@@ -579,4 +679,5 @@ }

const curation = await curateImage(base64, width, height, lang);
const cropBox = await masterCompose(base64, width, height, curation.master, lang);
const cropBox = await masterCompose(base64, width, height, curation.master, lang, curation);
const safeCrop = sanitizeCropBox(cropBox, width, height);
const cropped = !(safeCrop.x === 0 && safeCrop.y === 0 && safeCrop.width === width && safeCrop.height === height);

@@ -588,5 +689,17 @@ const profile = LEICA_PROFILES[curation.colorProfile] || LEICA_PROFILES.m10;

const isMinimal = curation.processingIntensity === 'minimal';
const shouldApplyGrain = !isMinimal
&& curation.grainAppropriate !== false
&& !!profile.grain;
const VIGNETTE_SCALE = { none: 0, subtle: 0.5, standard: 1.0, dramatic: 1.5 };
const effectiveVignetteLevel = isMinimal ? 'none' : curation.vignetteIntensity;
const vignetteScale = VIGNETTE_SCALE[effectiveVignetteLevel] ?? 1.0;
const overlays = [];
overlays.push({ input: Buffer.from(buildVignetteSvg(cw, ch, lens)), blend: 'multiply' });
if (profile.grain) {
if (vignetteScale > 0) {
const scaledLens = { ...lens, vignetteStrength: Math.min(0.9, lens.vignetteStrength * vignetteScale) };
overlays.push({ input: Buffer.from(buildVignetteSvg(cw, ch, scaledLens)), blend: 'multiply' });
}
if (shouldApplyGrain) {
const grainBuf = await generateFilmGrain(cw, ch, profile.grain);

@@ -599,5 +712,7 @@ if (grainBuf) overlays.push({ input: grainBuf, blend: 'soft-light' });

processed = applyLeicaColor(processed, profile);
processed = processed.sharpen({ sigma: lens.sharpenSigma, flat: lens.sharpenFlat, jagged: lens.sharpenJagged });
if (overlays.length > 0) {
processed = processed.composite(overlays);
}
const outputBuffer = await processed
.sharpen({ sigma: lens.sharpenSigma, flat: lens.sharpenFlat, jagged: lens.sharpenJagged })
.composite(overlays)
.withMetadata()

@@ -611,2 +726,11 @@ .jpeg({ quality: 92 })

const grainApplied = shouldApplyGrain;
const processingApplied = {
cropped,
grain_applied: grainApplied,
grain_rationale: curation.grainRationale || '',
vignette_level: effectiveVignetteLevel,
color_graded: true
};
const result = {

@@ -619,2 +743,8 @@ status: 'success',

coordinates: safeCrop,
composition_assessment: {
quality: curation.compositionQuality,
crop_applied: cropped,
crop_rationale: curation.cropRationale
},
processing_applied: processingApplied,
color_profile: profile.name,

@@ -642,11 +772,24 @@ color_rationale: curation.colorRationale,

if (lang === 'en') {
narrativeParts.push(`Recomposed through the eye of ${masterName} (${masterStyle}).`);
if (safeCrop.rule) narrativeParts.push(safeCrop.rule);
if (cropped) {
narrativeParts.push(`Recomposed through the eye of ${masterName} (${masterStyle}).`);
if (safeCrop.rule) narrativeParts.push(safeCrop.rule);
} else {
const quality = curation.cropRationale || 'strength in its original framing';
narrativeParts.push(`Your composition captures ${quality}.`);
}
narrativeParts.push(`Color grade: ${profile.name}.`);
narrativeParts.push(`Lens character: ${lensName}.`);
if (effectiveVignetteLevel !== 'none') {
narrativeParts.push(`Lens character: ${lensName}.`);
}
} else {
narrativeParts.push(`${masterName} · ${masterStyle}`);
if (safeCrop.rule) narrativeParts.push(safeCrop.rule);
if (cropped) {
narrativeParts.push(`${masterName} · ${masterStyle}`);
if (safeCrop.rule) narrativeParts.push(safeCrop.rule);
} else {
narrativeParts.push(curation.cropRationale || masterName);
}
narrativeParts.push(profile.name);
narrativeParts.push(lensName);
if (effectiveVignetteLevel !== 'none') {
narrativeParts.push(lensName);
}
}

@@ -653,0 +796,0 @@ result.presentation = narrativeParts.join('\n');

+1
-1
{
"name": "agentlux",
"version": "2.1.0",
"version": "3.0.0",
"description": "Multi-master Leica composition engine with dynamic color science, lens simulation, film grain, and decisive moment selection for autonomous vision agents.",

@@ -5,0 +5,0 @@ "main": "index.js",

+22
-13

@@ -20,2 +20,3 @@ # AgentLux

- **Only Opinionated.** No sliders. No presets menu. No "which filter do you want?" The Curator Agent analyzes your image and makes every creative decision autonomously.
- **Only Restrained.** A great curator knows when NOT to intervene. If your composition is already strong, it stays. If grain doesn't serve the image, it's skipped. Processing is proportional, never mechanical.

@@ -33,16 +34,20 @@ ## Architecture

│ · Lens character│ │ rule narrative │
└──────────────────┘ └────────┬─────────┘
┌──────────────────────┐
│ Image Pipeline │
│ (sharp, no AI) │
│ │ │ │
│ Assesses: │ │ OR: preserves │
│ · Composition │ │ original frame │
│ quality │ │ if excellent │
│ · Grain needed? │ └────────┬─────────┘
│ · Vignette │ │
│ intensity │ ▼
│ · Processing │ ┌──────────────────────┐
│ intensity │ │ Image Pipeline │
└──────────────────┘ │ (sharp, no AI) │
│ │
│ · Precision crop │
│ · Crop (if needed) │
│ · Leica color │
│ science (recomb) │
│ · Lens vignette + │
│ micro-contrast │
│ · Silver halide │
│ film grain │
│ science (always) │
│ · Vignette │
│ (if appropriate) │
│ · Film grain │
│ (if appropriate) │
└──────────┬───────────┘

@@ -55,3 +60,5 @@

│ · presentation text │
│ · full metadata │
│ · processing_applied│
│ · composition_ │
│ assessment │
└──────────────────────┘

@@ -119,2 +126,4 @@ ```

| `lens_rationale` | `string` | Why this lens character |
| `composition_assessment` | `object` | `{quality, crop_applied, crop_rationale}` — how the existing composition was evaluated. `quality`: `"excellent"` / `"good"` / `"fair"` / `"poor"` |
| `processing_applied` | `object` | `{cropped, grain_applied, grain_rationale, vignette_level, color_graded}` — what processing was actually applied |
| `burst_selection` | `object` | (Burst only) `{selected_index, total_images, rationale}` |

@@ -121,0 +130,0 @@ | `source_file_deletion` | `string` | `"deleted"` / `"partial"` / `"delete_failed"` / `"disabled"` |

@@ -55,6 +55,8 @@ # AgentLux

- `result.output_path` — The absolute path to the processed JPEG. **Send this file to the user.**
- `result.presentation` — A ready-to-use narrative explaining the creative decisions. **Show this text to the user.**
- `result.presentation` — A ready-to-use narrative explaining the creative decisions. **Show this text to the user.** The narrative is proportional: if the original composition was preserved, it says so honestly rather than fabricating a crop story.
- `result.master_photographer` — e.g. "Fan Ho"
- `result.master_style` — e.g. "Light & Shadow Geometry"
- `result.composition_rule` — e.g. "Diagonal shaft of light creates a natural leading line..."
- `result.composition_assessment` — `{quality, crop_applied, crop_rationale}` — how the system evaluated the original composition. `quality` is `"excellent"`, `"good"`, `"fair"`, or `"poor"`. When `quality` is `"excellent"`, the original framing is preserved.
- `result.processing_applied` — `{cropped, grain_applied, grain_rationale, vignette_level, color_graded}` — what was actually done to the image. Use this to understand the processing decisions.
- `result.color_profile` — e.g. "Leica M Monochrom"

@@ -124,5 +126,9 @@ - `result.lens_profile` — e.g. "Noctilux-M 50mm f/0.95 ASPH"

1. **Curator Agent** analyzes the image and selects the best-fit master photographer (Bresson, Alex Webb, Fan Ho, Koudelka, Salgado, Moriyama), Leica color profile (M10, M9, Monochrom, Tri-X, Portra), and lens character (Summilux, Noctilux, Summicron, Elmarit).
2. **Master Agent** embodies the selected photographer and computes the optimal crop.
3. **Image Pipeline** applies Leica color science, lens vignette + micro-contrast, and film grain.
4. Result is returned with a `presentation` narrative ready to show the user.
1. **Curator Agent** analyzes the image and makes all creative decisions in a single VLM pass:
- Selects the best-fit master photographer (Bresson, Alex Webb, Fan Ho, Koudelka, Salgado, Moriyama)
- Selects Leica color profile (M10, M9, Monochrom, Tri-X, Portra) and lens character (Summilux, Noctilux, Summicron, Elmarit)
- **Assesses composition quality** (`excellent` / `good` / `fair` / `poor`) — if the original composition is already strong, it is preserved
- **Decides effects intensity** — whether grain and vignette genuinely serve this image, or would feel like filters slapped on
2. **Master Agent** embodies the selected photographer and computes the optimal crop. If the Curator assessed the composition as excellent, this step is skipped and the original framing is preserved.
3. **Image Pipeline** applies Leica color science. Vignette and film grain are applied **conditionally** based on the Curator's aesthetic judgment — not mechanically.
4. Result is returned with a `presentation` narrative ready to show the user. The narrative is honest: it describes what was actually done, not what could have been done.