+30
-17
@@ -446,3 +446,3 @@ const fs = require('fs').promises; | ||
| async function curateImage(imageBase64, width, height) { | ||
| async function curateImage(imageBase64, width, height, lang) { | ||
| const provider = resolveProvider(process.env.AGENTLUX_CURATOR_MODEL); | ||
@@ -452,2 +452,3 @@ const masterList = Object.entries(MASTER_REGISTRY).map(([k, m]) => ` - "${k}": ${m.name} (${m.style})`).join('\n'); | ||
| 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.` : ''; | ||
@@ -472,3 +473,3 @@ 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. | ||
| Return ONLY a JSON object: | ||
| {"master": "key", "master_rationale": "brief why", "color_profile": "key", "color_rationale": "brief why", "lens": "key", "lens_rationale": "brief why"}`; | ||
| {"master": "key", "master_rationale": "brief why", "color_profile": "key", "color_rationale": "brief why", "lens": "key", "lens_rationale": "brief why"}${langInstruction}`; | ||
@@ -479,12 +480,14 @@ const request = buildVLMRequest(provider, prompt, imageBase64); | ||
| async function masterCompose(imageBase64, width, height, masterKey) { | ||
| async function masterCompose(imageBase64, width, height, masterKey, lang) { | ||
| const provider = resolveProvider(process.env.AGENTLUX_MASTER_MODEL); | ||
| const master = MASTER_REGISTRY[masterKey] || MASTER_REGISTRY.bresson; | ||
| const request = buildVLMRequest(provider, master.prompt(width, height), imageBase64); | ||
| 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); | ||
| return parseCropBox(await callVLM(request)); | ||
| } | ||
| async function selectDecisiveMoment(thumbnailsBase64) { | ||
| async function selectDecisiveMoment(thumbnailsBase64, lang) { | ||
| const provider = resolveProvider(process.env.AGENTLUX_SELECTOR_MODEL || process.env.AGENTLUX_CURATOR_MODEL); | ||
| const count = thumbnailsBase64.length; | ||
| const langInstruction = lang && lang !== 'en' ? `\n\nIMPORTANT: Write the "rationale" value in ${lang}.` : ''; | ||
| const prompt = `You are selecting The Decisive Moment from a burst of ${count} consecutive frames (indexed 0 to ${count - 1}). | ||
@@ -501,3 +504,3 @@ | ||
| Return ONLY a JSON object: | ||
| {"selected_index": int, "rationale": "Why THIS frame captures the unrepeatable instant"}`; | ||
| {"selected_index": int, "rationale": "Why THIS frame captures the unrepeatable instant"}${langInstruction}`; | ||
@@ -569,7 +572,8 @@ const request = buildMultiImageVLMRequest(provider, prompt, thumbnailsBase64); | ||
| const { width, height } = metadata; | ||
| const lang = context.lang || 'en'; | ||
| const vlmJpeg = await sharp(buffer).jpeg({ quality: 90 }).toBuffer(); | ||
| const base64 = vlmJpeg.toString('base64'); | ||
| const curation = await curateImage(base64, width, height); | ||
| const cropBox = await masterCompose(base64, width, height, curation.master); | ||
| const curation = await curateImage(base64, width, height, lang); | ||
| const cropBox = await masterCompose(base64, width, height, curation.master, lang); | ||
| const safeCrop = sanitizeCropBox(cropBox, width, height); | ||
@@ -629,8 +633,15 @@ | ||
| if (context.burstResult) { | ||
| narrativeParts.push(`Selected frame ${context.burstResult.selected_index + 1} of ${context.burstResult.total_images}: ${context.burstResult.rationale}`); | ||
| narrativeParts.push(context.burstResult.rationale); | ||
| } | ||
| narrativeParts.push(`Recomposed through the eye of ${masterName} (${masterStyle}).`); | ||
| if (safeCrop.rule) narrativeParts.push(safeCrop.rule); | ||
| narrativeParts.push(`Color grade: ${profile.name}.`); | ||
| narrativeParts.push(`Lens character: ${lensName}.`); | ||
| if (lang === 'en') { | ||
| narrativeParts.push(`Recomposed through the eye of ${masterName} (${masterStyle}).`); | ||
| if (safeCrop.rule) narrativeParts.push(safeCrop.rule); | ||
| narrativeParts.push(`Color grade: ${profile.name}.`); | ||
| narrativeParts.push(`Lens character: ${lensName}.`); | ||
| } else { | ||
| narrativeParts.push(`${masterName} · ${masterStyle}`); | ||
| if (safeCrop.rule) narrativeParts.push(safeCrop.rule); | ||
| narrativeParts.push(profile.name); | ||
| narrativeParts.push(lensName); | ||
| } | ||
| result.presentation = narrativeParts.join('\n'); | ||
@@ -645,3 +656,3 @@ | ||
| async function execute({ image_path, image_paths, output_path, delete_after = true }) { | ||
| async function execute({ image_path, image_paths, output_path, language, delete_after = true }) { | ||
| try { | ||
@@ -651,2 +662,3 @@ if (typeof delete_after !== 'boolean') { | ||
| } | ||
| const lang = (typeof language === 'string' && language.trim()) || process.env.AGENTLUX_LANGUAGE || 'en'; | ||
| if (output_path !== undefined) { | ||
@@ -706,3 +718,3 @@ if (typeof output_path !== 'string' || output_path.trim().length === 0) { | ||
| )); | ||
| const { selectedIndex, rationale } = await selectDecisiveMoment(thumbnails); | ||
| const { selectedIndex, rationale } = await selectDecisiveMoment(thumbnails, lang); | ||
@@ -716,3 +728,3 @@ const selected = loaded[selectedIndex]; | ||
| return await processImage(selected.buffer, meta, { | ||
| delete_after, deletionStatus, deletionMessage, outputPath: output_path, | ||
| lang, delete_after, deletionStatus, deletionMessage, outputPath: output_path, | ||
| burstResult: { selected_index: selectedIndex, total_images: image_paths.length, rationale } | ||
@@ -743,3 +755,3 @@ }); | ||
| return await processImage(buffer, metadata, { | ||
| delete_after, deletionStatus, deletionMessage, outputPath: output_path, burstResult: null | ||
| lang, delete_after, deletionStatus, deletionMessage, outputPath: output_path, burstResult: null | ||
| }); | ||
@@ -775,2 +787,3 @@ } catch (err) { | ||
| output_path: { type: 'string', description: 'Absolute path to write the output JPEG. If omitted, output is returned as image_data_uri (base64). Recommended for agent workflows.' }, | ||
| language: { type: 'string', description: 'Language for user-facing text (e.g. "zh", "ja", "fr", "de"). Defaults to "en". Pass the language the agent is conversing in.' }, | ||
| delete_after: { type: 'boolean', description: 'Delete original image(s) from disk after loading. Defaults to true (zero-retention).', default: true } | ||
@@ -777,0 +790,0 @@ }, |
+1
-1
| { | ||
| "name": "agentlux", | ||
| "version": "2.0.0", | ||
| "version": "2.1.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", |
+5
-1
@@ -77,2 +77,3 @@ # AgentLux | ||
| output_path: '/tmp/agentlux_out.jpg', // where to write the output JPEG | ||
| language: 'zh', // match your conversation language | ||
| delete_after: true // zero-retention: delete input after loading | ||
@@ -96,2 +97,3 @@ }); | ||
| | `output_path` | `string` | — | Absolute path to write output JPEG. If omitted, output is returned as `image_data_uri` (base64). **Recommended for agent workflows.** | | ||
| | `language` | `string` | `"en"` | Language for all user-facing text (e.g. `"zh"`, `"ja"`, `"fr"`). Pass the language the agent is conversing in. | | ||
| | `delete_after` | `boolean` | `true` | Delete original file(s) after loading into memory. | | ||
@@ -117,3 +119,3 @@ | ||
| | `burst_selection` | `object` | (Burst only) `{selected_index, total_images, rationale}` | | ||
| | `source_file_deletion` | `string` | `"deleted"` / `"delete_failed"` / `"disabled"` | | ||
| | `source_file_deletion` | `string` | `"deleted"` / `"partial"` / `"delete_failed"` / `"disabled"` | | ||
@@ -155,3 +157,5 @@ ## Error Response | ||
| |---|---|---| | ||
| | `AGENTLUX_LANGUAGE` | `en` | Default language for user-facing text. Overridden by `language` parameter per call. | | ||
| | `AGENTLUX_MAX_IMAGE_BYTES` | `31457280` (30MB) | Maximum input image size | | ||
| | `AGENTLUX_MAX_BURST_SIZE` | `20` | Maximum number of images in burst mode | | ||
| | `AGENTLUX_VLM_TIMEOUT_MS` | `15000` | VLM request timeout | | ||
@@ -158,0 +162,0 @@ | `AGENTLUX_VLM_MAX_RETRIES` | `2` | VLM retry count for transient errors | |
+2
-0
@@ -42,2 +42,3 @@ # AgentLux | ||
| output_path: "/tmp/agentlux_output_" + Date.now() + ".jpg", | ||
| language: "zh", // match the language you are conversing in (e.g. "en", "zh", "ja", "fr") | ||
| delete_after: true | ||
@@ -119,2 +120,3 @@ }); | ||
| | `AGENTLUX_MASTER_MODEL` | Optional | Override master model (e.g. `gpt-4o`) | | ||
| | `AGENTLUX_LANGUAGE` | Optional | Default language for user-facing text (e.g. `zh`, `ja`, `fr`). Overridden by `language` parameter. | | ||
@@ -121,0 +123,0 @@ ## What This Skill Does (For Context) |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
56899
3.18%703
1.88%184
2.22%1
-50%23
4.55%