@hraness/slopcamera 3.5.0 → 3.6.0
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/README.md +6 -5
- package/apps/desktop/application/operation.ts +1 -1
- package/apps/desktop/cli/args.ts +3 -1
- package/apps/desktop/cli/commands.ts +15 -1
- package/apps/desktop/cli/help.ts +17 -17
- package/apps/desktop/cli/menubar-status.ts +155 -0
- package/apps/desktop/cli/menubar.ts +88 -136
- package/apps/desktop/dist/cli/main.js +314 -324
- package/dist/cli.js +7 -8
- package/dist/generate.js +1 -1
- package/dist/{index-8bhp7cj2.js → index-4z9y2wa2.js} +1 -1
- package/dist/{index-24w75s6a.js → index-fevf4ppk.js} +1 -1
- package/dist/index-nxtmy18t.js +3 -0
- package/dist/{index-jab2mg0s.js → index-qxfexvj3.js} +2 -2
- package/dist/index.js +1 -1
- package/dist/operations.js +1 -1
- package/dist/workflow.js +1 -1
- package/package.json +2 -3
- package/skills/slopcamera/references/install.md +2 -2
- package/src/cli.ts +13 -10
- package/src/generate.ts +44 -1
- package/src/generation-pricing.ts +217 -0
- package/src/support.ts +6 -0
- package/src/version.ts +1 -1
- package/dist/index-77fjfg9f.js +0 -3
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
import{SLOPCAMERA_VERSION,VISUAL_STYLE_PROFILES,artifactSummary,checkDiagramFile,checkDrawingFile,createVisualStyleDirection,getVisualStyleProfile,renderDiagramFile,renderDrawingFile,runMcpServer,starterDrawingSource}from"./index-
|
|
3
|
+
import{SLOPCAMERA_VERSION,VISUAL_STYLE_PROFILES,artifactSummary,checkDiagramFile,checkDrawingFile,createVisualStyleDirection,getVisualStyleProfile,renderDiagramFile,renderDrawingFile,runMcpServer,starterDrawingSource}from"./index-qxfexvj3.js";import{installSkill,pathExists}from"./index-grk47tgs.js";import"./index-z1kd1x8j.js";import"./index-3cxf2c58.js";import"./index-4z9y2wa2.js";import{executeSlopcameraOperation,generateSlopcameraIcon,generateSlopcameraImageGallery,isSlopcameraOperationCode,parseSlopcameraGalleryVary,searchSlopcameraOperations,slopcameraGalleryKinds,slopcameraGalleryLimits,slopcameraIconMaximumRounds,slopcameraOperationCodes,withSlopcameraOperationHostAdmission}from"./index-fevf4ppk.js";import{vectorizeImage}from"./index-mnp0bftq.js";import{generateSlopcameraImageFile,slopcameraGatewayCredentialStatus,slopcameraImageModels}from"./index-nxtmy18t.js";import"./index-b3geqczn.js";import{__require}from"./index-80a36bc0.js";import{readFile,writeFile}from"fs/promises";import{resolve}from"path";function reportUsefulResult(observer){try{if(observer!==undefined)Promise.resolve(observer()).catch(()=>{return})}catch{}}import{maybeShowSupportInvitation,runSupportCommand,supportAdvancedHelp,supportHelpLine}from"@hraness/support-foundation/node";var slopcameraSupportHelpLine=()=>supportHelpLine({command:["slopcamera"]});var slopcameraSupportAdvancedHelp=()=>supportAdvancedHelp({command:["slopcamera"]});var supportProfile={id:"slopcamera",name:"Slopcamera",valueProposition:"Support ongoing development of local media tools for agents.",updates:false};function standaloneSupportEnvironment(){const env={...process.env};process.env.HRANESS_SUPPORT_AUDIENCE="off";process.env.HRANESS_SUPPORT_EMAIL="off";return env}async function runProductSupportCommand(args,options={}){const result=await runSupportCommand(supportProfile,args,{command:["slopcamera"],...options,gitEmail:false});if(result.stdout!=="")process.stdout.write(result.stdout);if(result.stderr!=="")process.stderr.write(result.stderr);return result.exitCode}async function showProductSupportInvitation(options={}){try{await maybeShowSupportInvitation(supportProfile,{usefulResult:true,command:["slopcamera"],...options,gitEmail:false})}catch{}}var slopcameraCliVersion=SLOPCAMERA_VERSION;var help=()=>`slopcamera ${slopcameraCliVersion}
|
|
4
4
|
|
|
5
5
|
Turn source material into deterministic diagrams, images, and canvas assets.
|
|
6
6
|
|
|
@@ -25,7 +25,6 @@ Usage:
|
|
|
25
25
|
slopcamera code execute <operation> --input <JSON>
|
|
26
26
|
slopcamera mcp --root <workspace>
|
|
27
27
|
slopcamera doctor
|
|
28
|
-
slopcamera support [--json|protocol --json|offer --json|shown <id>|release <id>|dismiss|snooze|enable|status --json]
|
|
29
28
|
slopcamera skill path
|
|
30
29
|
slopcamera skill install [--target codex|claude|agents] [--scope user|project] [--force]
|
|
31
30
|
|
|
@@ -65,9 +64,9 @@ into one labelled contact sheet plus a receipt. Use it to review texture,
|
|
|
65
64
|
skybox, backdrop, sprite, or design alternatives, then promote a chosen
|
|
66
65
|
candidate file explicitly \u2014 nothing is applied automatically.
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
${slopcameraSupportHelpLine()}
|
|
68
|
+
|
|
69
|
+
Advanced verbs, including the support protocol, live under \`slopcamera help advanced\`.
|
|
71
70
|
|
|
72
71
|
Code mode searches and executes a fixed semantic registry. Execute accepts
|
|
73
72
|
typed JSON for one exact owned operation code; it never evaluates source text.
|
|
@@ -80,12 +79,12 @@ stdout.
|
|
|
80
79
|
`,{flag:"wx"})}catch(error){if(error instanceof Error&&"code"in error&&error.code==="EEXIST"){throw new Error(`Refusing to overwrite existing file: ${filePath}`)}throw error}log(json?JSON.stringify({command:"diagram.sheets.init",source:filePath}):`Created ${filePath}`);reportUsefulResult(dependencies.onUsefulResult);return}if(command==="check"){const checks=await withSlopcameraOperationHostAdmission("slopcamera.diagram.check",async()=>await checkDrawingFile({filePath}),hostAdmissionOptions(dependencies));log(json?JSON.stringify({command:"diagram.sheets.check",source:filePath,...checks}):`Valid drawing: ${checks.sheetCount} sheet${checks.sheetCount===1?"":"s"} (${checks.paper}, ${checks.profile}).`);return}const result=await withSlopcameraOperationHostAdmission("slopcamera.diagram.render",async()=>await renderDrawingFile({filePath,...outDirectory===undefined?{}:{outDirectory}}),hostAdmissionOptions(dependencies));log(json?JSON.stringify({command:"diagram.sheets.render",artifacts:result.artifacts,checks:result.checks}):[result.artifacts.pdf,...result.artifacts.sheets,result.artifacts.receipt].join(`
|
|
81
80
|
`));reportUsefulResult(dependencies.onUsefulResult)}function canonicalArguments(args){const[surface,subcommand,...rest]=args;if(surface==="diagram"){if(subcommand==="init"||subcommand==="check"||subcommand==="render"){return[subcommand,...rest]}throw new Error("Use slopcamera diagram init, check, render, or sheets")}if(surface==="image"){if(subcommand==="vectorize"||subcommand==="generate"||subcommand==="icon"||subcommand==="gallery"){return[subcommand,...rest]}throw new Error("Use slopcamera image vectorize, generate, icon, or gallery")}if(surface==="init"||surface==="check"||surface==="render"||surface==="vectorize"||surface==="generate"){throw new Error(`The flat \`${surface}\` command moved to a namespaced Slopcamera surface.
|
|
82
81
|
|
|
83
|
-
${help}`)}return args}async function main(args,dependencies={}){if(args[0]==="style"){const[action,...words]=args.slice(1);const json=words.at(-1)==="--json";const positionals=json?words.slice(0,-1):words;const log=dependencies.log??console.log;if(action==="list"&&positionals.length===0){log(json?JSON.stringify({schemaVersion:1,styles:VISUAL_STYLE_PROFILES}):["Visual style foundations (authoring guidance; no effects are applied):",...VISUAL_STYLE_PROFILES.map((style)=>`${style.id} ${style.name}
|
|
82
|
+
${help()}`)}return args}async function main(args,dependencies={}){if(args[0]==="style"){const[action,...words]=args.slice(1);const json=words.at(-1)==="--json";const positionals=json?words.slice(0,-1):words;const log=dependencies.log??console.log;if(action==="list"&&positionals.length===0){log(json?JSON.stringify({schemaVersion:1,styles:VISUAL_STYLE_PROFILES}):["Visual style foundations (authoring guidance; no effects are applied):",...VISUAL_STYLE_PROFILES.map((style)=>`${style.id} ${style.name}
|
|
84
83
|
${style.summary}`),"Use slopcamera style show <id> for palette, cadence, materials, delivery, and review criteria."].join(`
|
|
85
|
-
`));return}if(action==="show"&&positionals.length===1&&!positionals[0].startsWith("-")){let style;try{style=getVisualStyleProfile(positionals[0])}catch{throw new Error(`Unknown visual style: ${JSON.stringify(positionals[0].slice(0,128))}. Use slopcamera style list.`)}log(json?JSON.stringify({schemaVersion:1,style}):createVisualStyleDirection(style.id));return}throw new Error("Use slopcamera style list [--json] or slopcamera style show <id> [--json].")}if(args[0]==="diagram"&&args[1]==="sheets"){await runDrawingSheets(args.slice(2),dependencies);return}if(args[0]==="support"){process.exitCode=await runProductSupportCommand(args.slice(1),dependencies.supportEnvironment===undefined?{}:{env:dependencies.supportEnvironment});return}const[command,...rest]=canonicalArguments(args);if(command===undefined||command==="help"||command==="
|
|
84
|
+
`));return}if(action==="show"&&positionals.length===1&&!positionals[0].startsWith("-")){let style;try{style=getVisualStyleProfile(positionals[0])}catch{throw new Error(`Unknown visual style: ${JSON.stringify(positionals[0].slice(0,128))}. Use slopcamera style list.`)}log(json?JSON.stringify({schemaVersion:1,style}):createVisualStyleDirection(style.id));return}throw new Error("Use slopcamera style list [--json] or slopcamera style show <id> [--json].")}if(args[0]==="diagram"&&args[1]==="sheets"){await runDrawingSheets(args.slice(2),dependencies);return}if(args[0]==="support"){process.exitCode=await runProductSupportCommand(args.slice(1),dependencies.supportEnvironment===undefined?{}:{env:dependencies.supportEnvironment});return}const[command,...rest]=canonicalArguments(args);if(command===undefined||command==="--help"||command==="-h"){console.log(help());return}if(command==="help"){console.log(rest[0]==="advanced"?slopcameraSupportAdvancedHelp():help());return}if(command==="version"||command==="--version"||command==="-v"){console.log(slopcameraCliVersion);return}if(command==="init"){const parsed=parseArguments(rest,new Set);const filePath=resolve(parsed.positionals[0]??"diagram.diagram.json");if(await pathExists(filePath))throw new Error(`Refusing to overwrite existing file: ${filePath}`);await writeFile(filePath,`${JSON.stringify(starter,null,2)}
|
|
86
85
|
`);console.log(`Created ${filePath}`);reportUsefulResult(dependencies.onUsefulResult);return}if(command==="check"){const parsed=parseArguments(rest,new Set(["config"]));const result=await withSlopcameraOperationHostAdmission("slopcamera.diagram.check",async()=>await checkDiagramFile({filePath:requiredPositional(parsed,0,"diagram file"),...parsed.options.config===undefined?{}:{configPath:parsed.options.config}}),hostAdmissionOptions(dependencies));console.log(`Valid diagram${result.configPath===null?"":` with ${result.configPath}`}.`);printFindings(result.findings);if(parsed.flags.has("strict")&&result.findings.length>0)process.exitCode=2;return}if(command==="render"){const parsed=parseArguments(rest,new Set(["out-dir","config","scale"]));const scale=parsed.options.scale===undefined?undefined:Number.parseFloat(parsed.options.scale);const result=await withSlopcameraOperationHostAdmission("slopcamera.diagram.render",async()=>await renderDiagramFile({filePath:requiredPositional(parsed,0,"diagram file"),...parsed.options["out-dir"]===undefined?{}:{outDirectory:parsed.options["out-dir"]},...parsed.options.config===undefined?{}:{configPath:parsed.options.config},...scale===undefined?{}:{scale}}),hostAdmissionOptions(dependencies));console.log(artifactSummary(result.artifacts));printFindings(result.findings);reportUsefulResult(dependencies.onUsefulResult);return}if(command==="vectorize"){const parsed=parseArguments(rest,new Set(["output","duotone","alpha-cutoff","timeout-ms"]));const unknownFlags=[...parsed.flags].filter((flag)=>flag!=="json");if(unknownFlags.length>0){throw new Error(`Unknown vectorize option: --${unknownFlags[0]}`)}if(parsed.positionals.length>1){throw new Error("slopcamera image vectorize accepts exactly one raster input")}const output=requiredOption(parsed,"output");if(!output.toLowerCase().endsWith(".svg")){throw new Error("--output must end in .svg")}const alphaCutoff=parsePositiveInteger(parsed.options["alpha-cutoff"],"alpha-cutoff");const timeoutMs=parsePositiveInteger(parsed.options["timeout-ms"],"timeout-ms");const duotone=parseDuotone(parsed.options.duotone);const result=await withSlopcameraOperationHostAdmission("slopcamera.image.vectorize",async(lease)=>await(dependencies.vectorize??vectorizeImage)(requiredPositional(parsed,0,"raster image"),{...alphaCutoff===undefined?{}:{alphaCutoff},...duotone===undefined?{}:{duotone},...timeoutMs===undefined?{}:{limits:{maxDurationMs:timeoutMs}},inheritedFileDescriptors:[lease.inheritedFileDescriptor],outputPath:output}),hostAdmissionOptions(dependencies));if(parsed.flags.has("json")){(dependencies.log??console.log)(JSON.stringify({...result.receipt,outputPath:result.outputPath},null,2))}else{(dependencies.log??console.log)(`Vectorized ${result.receipt.width}\xD7${result.receipt.height} with `+`${result.receipt.profile}/${result.receipt.representation}: ${result.outputPath}`)}reportUsefulResult(dependencies.onUsefulResult);return}if(command==="generate"){const parsed=parseArguments(rest,new Set(["model","output"]));const unknownFlags=[...parsed.flags].filter((flag)=>flag!=="json");if(unknownFlags.length>0){throw new Error(`Unknown generate option: --${unknownFlags[0]}`)}if(parsed.positionals.length!==1){throw new Error("slopcamera image generate accepts exactly one prompt")}const model=parsed.options.model??slopcameraImageModels[1];if(model.length>256||!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(model)){throw new Error("--model must be a bounded Vercel AI Gateway provider/model id")}const result=await withSlopcameraOperationHostAdmission("slopcamera.image.generate",async()=>await(dependencies.generate??generateSlopcameraImageFile)({model,prompt:requiredPositional(parsed,0,"prompt"),outputPath:requiredOption(parsed,"output")}),hostAdmissionOptions(dependencies));if(parsed.flags.has("json")){(dependencies.log??console.log)(JSON.stringify(result,null,2))}else{(dependencies.log??console.log)(`Generated ${result.mediaType} with ${result.model}: ${result.outputPath} (${result.bytes} bytes, request ${result.requestId})`)}reportUsefulResult(dependencies.onUsefulResult);return}if(command==="icon"){const parsed=parseArguments(rest,new Set(["model","output","ink","purpose","rounds","critique-model"]));const unknownFlags=[...parsed.flags].filter((flag)=>flag!=="json"&&flag!=="keep-raster");if(unknownFlags.length>0){throw new Error(`Unknown icon option: --${unknownFlags[0]}`)}if(parsed.positionals.length!==1){throw new Error("slopcamera image icon accepts exactly one subject")}const output=requiredOption(parsed,"output");if(!output.toLowerCase().endsWith(".svg")){throw new Error("--output must end in .svg")}const model=parsed.options.model??slopcameraImageModels[1];if(model.length>256||!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(model)){throw new Error("--model must be a bounded Vercel AI Gateway provider/model id")}const critiqueModel=parsed.options["critique-model"];if(critiqueModel!==undefined&&(critiqueModel.length>256||!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(critiqueModel))){throw new Error("--critique-model must be a bounded Vercel AI Gateway provider/model id")}const ink=parsed.options.ink;if(ink!==undefined&&!/^#[a-f0-9]{3}(?:[a-f0-9]{3})?$/iu.test(ink)){throw new Error("--ink must be a #rgb or #rrggbb color")}const purpose=parsed.options.purpose;if(purpose!==undefined&&purpose!=="mark"&&purpose!=="illustration"){throw new Error("--purpose must be mark or illustration")}const rounds=parsePositiveInteger(parsed.options.rounds,"rounds");if(rounds!==undefined&&rounds>slopcameraIconMaximumRounds){throw new Error(`--rounds must be at most ${slopcameraIconMaximumRounds}`)}const result=await withSlopcameraOperationHostAdmission("slopcamera.image.icon",async(lease)=>await(dependencies.icon??generateSlopcameraIcon)({keepRaster:parsed.flags.has("keep-raster"),model,outputPath:output,subject:requiredPositional(parsed,0,"subject"),inheritedFileDescriptors:[lease.inheritedFileDescriptor],...critiqueModel===undefined?{}:{critiqueModel},...ink===undefined?{}:{ink},...purpose===undefined?{}:{purpose},...rounds===undefined?{}:{rounds}}),hostAdmissionOptions(dependencies));if(parsed.flags.has("json")){(dependencies.log??console.log)(JSON.stringify(result,null,2))}else{const scored=result.attempts.map((attempt)=>attempt.score===undefined?`#${attempt.round}`:`#${attempt.round}=${attempt.score}`).join(" ");(dependencies.log??console.log)(`Icon ${result.svgSha256.slice(0,12)} round ${result.selectedRound}/${result.attempts.length} (${scored}): ${result.outputPath}`)}return}if(command==="gallery"){const parsed=parseArguments(rest,new Set(["model","output-dir","kind","count","vary","candidates","cell","timeout-ms"]));const unknownFlags=[...parsed.flags].filter((flag)=>flag!=="json"&&flag!=="tile"&&flag!=="no-tile");if(unknownFlags.length>0){throw new Error(`Unknown gallery option: --${unknownFlags[0]}`)}if(parsed.flags.has("tile")&&parsed.flags.has("no-tile")){throw new Error("--tile and --no-tile are mutually exclusive")}if(parsed.positionals.length!==1){throw new Error("slopcamera image gallery accepts exactly one subject")}const model=parsed.options.model??slopcameraImageModels[1];if(model.length>256||!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(model)){throw new Error("--model must be a bounded Vercel AI Gateway provider/model id")}const kind=parsed.options.kind;if(kind!==undefined&&!slopcameraGalleryKinds.includes(kind)){throw new Error(`--kind must be one of: ${slopcameraGalleryKinds.join(", ")}`)}const count=parsePositiveInteger(parsed.options.count,"count");if(count!==undefined&&count>slopcameraGalleryLimits.candidates){throw new Error(`--count must be at most ${slopcameraGalleryLimits.candidates}`)}const cell=parsePositiveInteger(parsed.options.cell,"cell");if(cell!==undefined&&(cell<slopcameraGalleryLimits.cellEdgeMin||cell>slopcameraGalleryLimits.cellEdgeMax)){throw new Error(`--cell must be between ${slopcameraGalleryLimits.cellEdgeMin} and ${slopcameraGalleryLimits.cellEdgeMax}`)}const timeoutMs=parsePositiveInteger(parsed.options["timeout-ms"],"timeout-ms");const vary=parseGalleryVary(parsed.options.vary);const candidates=parsed.options.candidates===undefined?undefined:await readGalleryCandidates(parsed.options.candidates);if(candidates!==undefined&&!Array.isArray(candidates)){throw new Error("--candidates JSON must be an array of {id, prompt|variant} entries")}const result=await withSlopcameraOperationHostAdmission("slopcamera.image.gallery",async()=>await(dependencies.gallery??generateSlopcameraImageGallery)({subject:requiredPositional(parsed,0,"subject"),outputDir:requiredOption(parsed,"output-dir"),model,...kind===undefined?{}:{kind},...count===undefined?{}:{count},...vary===undefined?{}:{vary},...candidates===undefined?{}:{candidates},...cell===undefined?{}:{cellEdge:cell},...timeoutMs===undefined?{}:{timeoutMs},...parsed.flags.has("tile")?{tiled:true}:parsed.flags.has("no-tile")?{tiled:false}:{}}),hostAdmissionOptions(dependencies));if(parsed.flags.has("json")){(dependencies.log??console.log)(JSON.stringify(result,null,2))}else{const rows=result.candidates.map((candidate)=>candidate.status==="generated"?`#${candidate.index} ${candidate.id} ${candidate.path}`:`#${candidate.index} ${candidate.id} failed`);(dependencies.log??console.log)(`Gallery ${result.counts.generated}/${result.counts.requested} generated (${result.model}): ${result.gallery.path}
|
|
87
86
|
`+`${rows.join(`
|
|
88
87
|
`)}
|
|
89
88
|
receipt ${result.receiptPath}`)}reportUsefulResult(dependencies.onUsefulResult);return}if(command==="code"){const[subcommand,...subcommandArgs]=rest;if(subcommand==="search"){const parsed=parseArguments(subcommandArgs,new Set(["limit"]));if(parsed.flags.size>0||parsed.positionals.length>1){throw new Error("Use slopcamera code search [query] [--limit <number>]")}const limit=parsePositiveInteger(parsed.options.limit,"limit")??slopcameraOperationCodes.length;const operations=searchSlopcameraOperations(parsed.positionals[0]??"",limit);console.log(JSON.stringify({operations},null,2));return}if(subcommand==="execute"){const parsed=parseArguments(subcommandArgs,new Set(["input"]));if(parsed.flags.size>0||parsed.positionals.length!==1){throw new Error("Use slopcamera code execute <operation> --input <JSON>")}const requestedOperation=parsed.positionals[0];const operation=isSlopcameraOperationCode(requestedOperation)?requestedOperation:undefined;if(operation===undefined){throw new Error(`Unknown Slopcamera operation code: ${requestedOperation}`)}const inputText=requiredOption(parsed,"input");if(Buffer.byteLength(inputText,"utf8")>64*1024){throw new Error("--input JSON must be no more than 65536 UTF-8 bytes")}let input;try{input=JSON.parse(inputText)}catch{throw new Error("--input must be valid JSON")}const result=await executeSlopcameraOperation(operation,input,{...hostAdmissionOptions(dependencies)});(dependencies.log??console.log)(JSON.stringify({operation,result},null,2));if(operation==="slopcamera.diagram.render"||operation==="slopcamera.image.vectorize"||operation==="slopcamera.image.generate"){reportUsefulResult(dependencies.onUsefulResult)}return}throw new Error("Use slopcamera code search [query] or slopcamera code execute <operation> --input <JSON>")}if(command==="mcp"){const parsed=parseArguments(rest,new Set(["root"]));if(parsed.positionals.length>0||parsed.flags.size>0){throw new Error("slopcamera mcp accepts only --root <workspace>")}await runMcpServer({rootDirectory:requiredOption(parsed,"root"),serverVersion:slopcameraCliVersion});return}if(command==="doctor"){console.log(`slopcamera ${slopcameraCliVersion}`);console.log(`Bun ${process.versions.bun??"not detected"}`);console.log("Headless diagram SVG/PNG/tldraw renderer ready");console.log(process.platform==="win32"?"Local raster-to-SVG vectorizer unavailable on Windows (fails closed with tool_platform)":"Local raster-to-SVG vectorizer ready without authentication (VTracer downloads on first use)");console.log("Root-relative MCP check/render/scene server ready (trusted local workspace)");const gateway=slopcameraGatewayCredentialStatus();console.log(gateway.available?`Vercel AI Gateway ready via ${gateway.source}`:"Vercel AI Gateway requires AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN");return}if(command==="skill"){const[subcommand,...subcommandArgs]=rest;if(subcommand==="path"){const{bundledSkillPath}=await import("./skill-install-gx7afxsd.js");console.log(bundledSkillPath());return}if(subcommand==="install"){const parsed=parseArguments(subcommandArgs,new Set(["target","scope","project"]));const target=parsed.options.target??"codex";const scope=parsed.options.scope??"user";if(!["codex","claude","agents"].includes(target)){throw new Error("--target must be codex, claude, or agents")}if(!["user","project"].includes(scope)){throw new Error("--scope must be user or project")}const destination=await installSkill({target,scope,...parsed.options.project===undefined?{}:{projectDirectory:parsed.options.project},force:parsed.flags.has("force")});console.log(`Installed slopcamera skill at ${destination}`);return}throw new Error("Use slopcamera skill path or install")}throw new Error(`Unknown command: ${command}
|
|
90
89
|
|
|
91
|
-
${help}`)}if(import.meta.main){try{const env=standaloneSupportEnvironment();let usefulResult=false;await main(process.argv.slice(2),{supportEnvironment:env,onUsefulResult:()=>{usefulResult=true}});if(usefulResult&&(process.exitCode??0)===0)await showProductSupportInvitation({env})}catch(error){console.error(error instanceof Error?error.message:String(error));process.exitCode=1}}export{slopcameraCliVersion,main};
|
|
90
|
+
${help()}`)}if(import.meta.main){try{const env=standaloneSupportEnvironment();let usefulResult=false;await main(process.argv.slice(2),{supportEnvironment:env,onUsefulResult:()=>{usefulResult=true}});if(usefulResult&&(process.exitCode??0)===0)await showProductSupportInvitation({env})}catch(error){console.error(error instanceof Error?error.message:String(error));process.exitCode=1}}export{slopcameraCliVersion,main};
|
package/dist/generate.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{createFixedGatewayFetch,generateSlopcameraImage,generateSlopcameraImageFile,resolveSlopcameraGatewayCredential,slopcameraGatewayApiBaseUrl,slopcameraGatewayCredentialStatus,slopcameraImageModels,slopcameraMaximumPromptBytes,slopcameraMaximumRawImageBytes,slopcameraResponseMediaTypes}from"./index-
|
|
2
|
+
import{createFixedGatewayFetch,generateSlopcameraImage,generateSlopcameraImageFile,resolveSlopcameraGatewayCredential,slopcameraGatewayApiBaseUrl,slopcameraGatewayCredentialStatus,slopcameraImageModels,slopcameraMaximumPromptBytes,slopcameraMaximumRawImageBytes,slopcameraResponseMediaTypes}from"./index-nxtmy18t.js";import"./index-80a36bc0.js";export{slopcameraResponseMediaTypes,slopcameraMaximumRawImageBytes,slopcameraMaximumPromptBytes,slopcameraImageModels,slopcameraGatewayCredentialStatus,slopcameraGatewayApiBaseUrl,resolveSlopcameraGatewayCredential,generateSlopcameraImageFile,generateSlopcameraImage,createFixedGatewayFetch};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{executeSlopcameraOperationWithLease,isSlopcameraOperationCode,parseSlopcameraOperationInput,slopcameraOperationHostResourceClaims}from"./index-
|
|
2
|
+
import{executeSlopcameraOperationWithLease,isSlopcameraOperationCode,parseSlopcameraOperationInput,slopcameraOperationHostResourceClaims}from"./index-fevf4ppk.js";import{createDefaultHostResourceCoordinator}from"./index-b3geqczn.js";var workflowIdPattern=/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/u;var workflowStepIdPattern=/^[A-Za-z0-9]+(?:[._:-][A-Za-z0-9]+)*$/u;var defaultMaximumSteps=64;var hardMaximumSteps=256;class SlopcameraWorkflowError extends Error{code;completedSteps;failedStep;constructor(code,message,options={}){super(`[${code}] ${message}`,{cause:options.cause});this.name="SlopcameraWorkflowError";this.code=code;this.completedSteps=Object.freeze([...options.completedSteps??[]].sort((left,right)=>left.index-right.index));if(options.failedStep!==undefined){this.failedStep=Object.freeze({...options.failedStep})}}}function workflowError(code,message){throw new SlopcameraWorkflowError(code,message)}function validateWorkflowId(id){if(typeof id!=="string"||id.length<1||id.length>80||!workflowIdPattern.test(id)){workflowError("INVALID_WORKFLOW","Workflow id must be 1 through 80 lowercase letters, numbers, dots, underscores, or hyphens.")}}function validateStepId(id,completedSteps=[]){if(typeof id!=="string"||id.length<1||id.length>80||!workflowStepIdPattern.test(id)){throw new SlopcameraWorkflowError("INVALID_WORKFLOW_STEP","Step id must be 1 through 80 letters, numbers, dots, underscores, colons, or hyphens.",{completedSteps})}}function defineSlopcameraWorkflow(options){if(typeof options!=="object"||options===null){workflowError("INVALID_WORKFLOW","Workflow definition must be an object.")}validateWorkflowId(options.id);if(!Number.isSafeInteger(options.version)||options.version<1){workflowError("INVALID_WORKFLOW","Workflow version must be a positive safe integer.")}if(typeof options.parseInput!=="function"||typeof options.run!=="function"){workflowError("INVALID_WORKFLOW","Workflow definition requires parseInput and run functions.")}return Object.freeze({id:options.id,version:options.version,parseInput:options.parseInput,run:options.run})}function maximumSteps(value){const resolved=value??defaultMaximumSteps;if(!Number.isSafeInteger(resolved)||resolved<1||resolved>hardMaximumSteps){workflowError("INVALID_WORKFLOW",`maximumSteps must be an integer from 1 through ${hardMaximumSteps}.`)}return resolved}function aborted(completedSteps,cause){return new SlopcameraWorkflowError("WORKFLOW_ABORTED","Workflow execution was aborted.",{cause,completedSteps})}async function runSlopcameraWorkflow(definition,value,options={}){const normalized=defineSlopcameraWorkflow(definition);const limit=maximumSteps(options.maximumSteps);const signal=options.signal??options.dependencies?.signal??new AbortController().signal;const invoked=new Set;const completed=[];const dispatched=[];let acceptingOperations=true;let nextIndex=0;if(signal.aborted)throw aborted(completed);let input;try{input=normalized.parseInput(value)}catch(cause){throw new SlopcameraWorkflowError("INVALID_WORKFLOW_INPUT","Workflow input did not satisfy its parser.",{cause})}const executor=options.executor??((code,operationInput,context2)=>executeSlopcameraOperationWithLease(code,operationInput,context2.hostResourceLease,options.dependencies));const hostResourceCoordinator=options.hostResourceCoordinator??options.dependencies?.hostResourceCoordinator??createDefaultHostResourceCoordinator();const waitTimeoutMilliseconds=options.waitTimeoutMilliseconds??options.dependencies?.waitTimeoutMilliseconds;async function dispatchOperation(id,code,operationInput){if(signal.aborted)throw aborted(completed);validateStepId(id,completed);if(!isSlopcameraOperationCode(code)){throw new SlopcameraWorkflowError("INVALID_WORKFLOW_STEP",`Workflow step ${id} names an unknown Slopcamera operation.`,{completedSteps:completed})}if(invoked.has(id)){throw new SlopcameraWorkflowError("INVALID_WORKFLOW_STEP",`Duplicate workflow step id: ${id}.`,{completedSteps:completed})}if(nextIndex>=limit){throw new SlopcameraWorkflowError("INVALID_WORKFLOW_STEP",`Workflow exceeds its ${String(limit)}-step limit.`,{completedSteps:completed})}const index=nextIndex;nextIndex+=1;invoked.add(id);let normalizedInput;try{normalizedInput=parseSlopcameraOperationInput(code,operationInput)}catch(cause){throw new SlopcameraWorkflowError("INVALID_WORKFLOW_STEP",`Workflow step ${id} has invalid input for ${code}.`,{cause,completedSteps:completed})}try{const result=await hostResourceCoordinator.withLease(slopcameraOperationHostResourceClaims(code),async(hostResourceLease)=>await executor(code,normalizedInput,{hostResourceLease,signal,stepId:id}),{signal,...waitTimeoutMilliseconds===undefined?{}:{waitTimeoutMilliseconds}});completed.push(Object.freeze({id,index,operation:code}));if(signal.aborted)throw aborted(completed);return result}catch(cause){if(cause instanceof SlopcameraWorkflowError&&cause.code==="WORKFLOW_ABORTED"){throw cause}if(signal.aborted)throw aborted(completed,cause);throw new SlopcameraWorkflowError("WORKFLOW_STEP_FAILED",`Workflow step ${id} (${code}) failed.`,{cause,completedSteps:completed,failedStep:{id,operation:code}})}}const context=Object.freeze({signal,operation(id,code,operationInput){if(!acceptingOperations){const closed=Promise.reject(new SlopcameraWorkflowError("INVALID_WORKFLOW_STEP","Workflow operations cannot start after authored workflow code has settled.",{completedSteps:completed}));closed.catch(()=>{return});return closed}const operation=dispatchOperation(id,code,operationInput);dispatched.push(operation);operation.catch(()=>{return});return operation}});let output;let runFailed=false;let runFailure;try{output=await normalized.run(context,input)}catch(cause){runFailed=true;runFailure=cause}finally{acceptingOperations=false}const operationResults=await Promise.allSettled(dispatched);if(signal.aborted)throw aborted(completed,runFailed?runFailure:undefined);if(runFailed){if(runFailure instanceof SlopcameraWorkflowError)throw runFailure;throw new SlopcameraWorkflowError("WORKFLOW_FAILED",`Workflow ${normalized.id} failed in authored code.`,{cause:runFailure,completedSteps:completed})}const operationFailure=operationResults.find((result)=>result.status==="rejected");if(operationFailure!==undefined){const cause=operationFailure.reason;if(cause instanceof SlopcameraWorkflowError)throw cause;throw new SlopcameraWorkflowError("WORKFLOW_STEP_FAILED","A dispatched workflow operation failed.",{cause,completedSteps:completed})}return Object.freeze({workflow:Object.freeze({id:normalized.id,version:normalized.version}),output,steps:Object.freeze([...completed].sort((left,right)=>left.index-right.index))})}
|
|
3
3
|
export{SlopcameraWorkflowError,defineSlopcameraWorkflow,runSlopcameraWorkflow};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{VectorizeDeadline,encodeTracePng,loadRaster,normalizedHexColor,parseHexColor,resolveVectorizeLimits,vectorizeHardLimits,vectorizeImage}from"./index-mnp0bftq.js";import{SlopcameraCloudError,createFixedGatewayFetch,generateSlopcameraImage,generateSlopcameraImageFile,resolveSlopcameraGatewayCredential,slopcameraGatewayApiBaseUrl,slopcameraImageModels,slopcameraMaximumPromptBytes}from"./index-
|
|
2
|
+
import{VectorizeDeadline,encodeTracePng,loadRaster,normalizedHexColor,parseHexColor,resolveVectorizeLimits,vectorizeHardLimits,vectorizeImage}from"./index-mnp0bftq.js";import{SlopcameraCloudError,createFixedGatewayFetch,generateSlopcameraImage,generateSlopcameraImageFile,resolveSlopcameraGatewayCredential,slopcameraGatewayApiBaseUrl,slopcameraImageModels,slopcameraMaximumPromptBytes}from"./index-nxtmy18t.js";import{createDefaultHostResourceCoordinator}from"./index-b3geqczn.js";import{__require}from"./index-80a36bc0.js";import{randomUUID as randomUUID3}from"crypto";import{mkdir as mkdir3,readFile as readFile2,rename as rename2,rm as rm3,writeFile as writeFile3}from"fs/promises";import{dirname as dirname3,join as join2,resolve as resolve3}from"path";var shared='fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"';var builtInIcons=Object.freeze({brain:{viewBox:"0 0 24 24",body:`<path ${shared} d="M9.5 4.5A3.5 3.5 0 0 0 6 8v.35A3.5 3.5 0 0 0 5.5 15 3.5 3.5 0 0 0 9 19.5M14.5 4.5A3.5 3.5 0 0 1 18 8v.35a3.5 3.5 0 0 1 .5 6.65 3.5 3.5 0 0 1-3.5 4.5M9.5 4.5v15M14.5 4.5v15M6 9.5h3.5M14.5 14.5H18"/>`},check:{viewBox:"0 0 24 24",body:`<path ${shared} d="m5 12 4.25 4.25L19 6.5"/>`},code:{viewBox:"0 0 24 24",body:`<path ${shared} d="m8.5 7-5 5 5 5M15.5 7l5 5-5 5M13.5 4l-3 16"/>`},database:{viewBox:"0 0 24 24",body:`<ellipse ${shared} cx="12" cy="5" rx="7.5" ry="3"/><path ${shared} d="M4.5 5v7c0 1.66 3.36 3 7.5 3s7.5-1.34 7.5-3V5M4.5 12v7c0 1.66 3.36 3 7.5 3s7.5-1.34 7.5-3v-7"/>`},document:{viewBox:"0 0 24 24",body:`<path ${shared} d="M6 2.75h8l4 4V21.25H6zM14 2.75v4h4M9 11h6M9 15h6"/>`},globe:{viewBox:"0 0 24 24",body:`<circle ${shared} cx="12" cy="12" r="9"/><path ${shared} d="M3 12h18M12 3c2.4 2.45 3.5 5.45 3.5 9S14.4 18.55 12 21M12 3C9.6 5.45 8.5 8.45 8.5 12s1.1 6.55 3.5 9"/>`},search:{viewBox:"0 0 24 24",body:`<circle ${shared} cx="10.5" cy="10.5" r="6.5"/><path ${shared} d="m15.25 15.25 5 5"/>`},server:{viewBox:"0 0 24 24",body:`<rect ${shared} x="3" y="3.5" width="18" height="7" rx="2"/><rect ${shared} x="3" y="13.5" width="18" height="7" rx="2"/><path ${shared} d="M7 7h.01M7 17h.01M11 7h6M11 17h6"/>`},shield:{viewBox:"0 0 24 24",body:`<path ${shared} d="M12 2.75 20 6v5.5c0 5.25-3.35 8.2-8 10-4.65-1.8-8-4.75-8-10V6z"/>`},user:{viewBox:"0 0 24 24",body:`<circle ${shared} cx="12" cy="8" r="4"/><path ${shared} d="M4.5 21c.5-4.2 3-6.5 7.5-6.5s7 2.3 7.5 6.5"/>`}});function sanitizeIcon(icon){const dangerous=/<\s*(script|style|foreignObject|iframe|object|embed|image|use|a)\b|on[a-z]+\s*=|(?:xlink:)?href\s*=|style\s*=|javascript:|url\s*\(/i;if(dangerous.test(icon.body)){throw new Error("Icon body contains executable or externally embedded SVG content")}if(!/^[-+.\d\s]+$/.test(icon.viewBox.trim())){throw new Error(`Invalid icon viewBox: ${icon.viewBox}`)}return icon}var defaultLabelFontSize=22;var defaultLabelWeight=600;var defaultLineHeightRatio=1.35;var defaultRowGap=8;var iconLabelGap=12;function characterWidthRatio(fontFamily){return fontFamily==="mono"?0.62:0.56}function estimateDiagramTextWidth(text,fontSize,fontFamily="default"){return text.length*fontSize*characterWidthRatio(fontFamily)}function wrapDiagramText(text,maxWidth,fontSize,fontFamily="default"){const explicitLines=text.split(`
|
|
3
3
|
`);const maxCharacters=Math.max(1,Math.floor(maxWidth/(fontSize*characterWidthRatio(fontFamily))));const lines=[];for(const explicitLine of explicitLines){if(explicitLine.length<=maxCharacters){lines.push(explicitLine);continue}const words=explicitLine.split(/\s+/);let current="";for(const word of words){const candidate=current===""?word:`${current} ${word}`;if(candidate.length<=maxCharacters||current===""){current=candidate}else{lines.push(current);current=word}}if(current!=="")lines.push(current)}return lines.length===0?[""]:lines}function resolveRows(shape){if(shape.labelRows!==undefined){return shape.labelRows.map((row)=>({text:row.text,fontSize:row.fontSize??defaultLabelFontSize,fontFamily:row.fontFamily??"default",weight:row.weight??defaultLabelWeight,lineHeight:(row.fontSize??defaultLabelFontSize)*defaultLineHeightRatio}))}if(shape.label===undefined)return[];const fontSize=shape.labelFontSize??defaultLabelFontSize;return[{text:shape.label,fontSize,fontFamily:shape.labelFontFamily??"default",weight:shape.labelWeight??defaultLabelWeight,lineHeight:fontSize*defaultLineHeightRatio}]}function layoutBoxContent(shape,resolvedIconSize){const textWidth=Math.max(1,shape.width-32);const pendingRows=resolveRows(shape).map((row)=>{const lines=wrapDiagramText(row.text,textWidth,row.fontSize,row.fontFamily);const height=lines.length*row.lineHeight;return{...row,lines,height}});const rowGap=shape.labelRows===undefined?0:shape.labelRowGap??defaultRowGap;const rowsHeight=pendingRows.reduce((total,row)=>total+row.height,0)+Math.max(0,pendingRows.length-1)*rowGap;const hasIcon=resolvedIconSize!==undefined;const gap=hasIcon&&pendingRows.length>0?iconLabelGap:0;const contentHeight=(resolvedIconSize??0)+gap+rowsHeight;let cursorY=shape.y+(shape.height-contentHeight)/2;const icon=resolvedIconSize===undefined?undefined:{x:shape.x+(shape.width-resolvedIconSize)/2,y:cursorY,size:resolvedIconSize};if(hasIcon)cursorY+=resolvedIconSize+gap;const rows=pendingRows.map((row)=>{const resolved={...row,y:cursorY};cursorY+=row.height+rowGap;return resolved});return{...icon===undefined?{}:{icon},rows}}import{readFile}from"fs/promises";import{extname,isAbsolute}from"path";import{fileURLToPath}from"url";import{Resvg}from"@resvg/resvg-js";var NebulaSans_Bold_default="./NebulaSans-Bold-26se8aek.otf";var NebulaSans_Bold_default2="./NebulaSans-Bold-bcz7y08t.woff2";var NebulaSans_Book_default="./NebulaSans-Book-8cenzchw.otf";var NebulaSans_Book_default2="./NebulaSans-Book-5ax05zvn.woff2";var tones=["neutral","blue","orange","green","red","purple","yellow"];var defaults={light:{background:"#ffffff",foreground:"#18181b",muted:"#71717a",stroke:"#27272a",tones:{neutral:{fill:"#f4f4f5",stroke:"#52525b",text:"#18181b"},blue:{fill:"#dbeafe",stroke:"#2563eb",text:"#1e3a8a"},orange:{fill:"#ffedd5",stroke:"#ea580c",text:"#7c2d12"},green:{fill:"#dcfce7",stroke:"#16a34a",text:"#14532d"},red:{fill:"#fee2e2",stroke:"#dc2626",text:"#7f1d1d"},purple:{fill:"#f3e8ff",stroke:"#9333ea",text:"#581c87"},yellow:{fill:"#fef9c3",stroke:"#ca8a04",text:"#713f12"}}},dark:{background:"#09090b",foreground:"#fafafa",muted:"#a1a1aa",stroke:"#d4d4d8",tones:{neutral:{fill:"#27272a",stroke:"#a1a1aa",text:"#fafafa"},blue:{fill:"#172554",stroke:"#60a5fa",text:"#dbeafe"},orange:{fill:"#431407",stroke:"#fb923c",text:"#ffedd5"},green:{fill:"#052e16",stroke:"#4ade80",text:"#dcfce7"},red:{fill:"#450a0a",stroke:"#f87171",text:"#fee2e2"},purple:{fill:"#3b0764",stroke:"#c084fc",text:"#f3e8ff"},yellow:{fill:"#422006",stroke:"#facc15",text:"#fef9c3"}}}};function mergeTheme(base,override){if(override===undefined)return base;return{background:override.background??base.background,foreground:override.foreground??base.foreground,muted:override.muted??base.muted,stroke:override.stroke??base.stroke,tones:Object.fromEntries(tones.map((tone)=>[tone,{fill:override.tones?.[tone]?.fill??base.tones[tone].fill,stroke:override.tones?.[tone]?.stroke??base.tones[tone].stroke,text:override.tones?.[tone]?.text??base.tones[tone].text}]))}}function resolveTheme(mode,config){return mergeTheme(defaults[mode],config.theme?.[mode])}var bundledAssetPath=(asset)=>isAbsolute(asset)?asset:fileURLToPath(new URL(asset,import.meta.url));var defaultFont=Object.freeze({family:"Nebula Sans",files:Object.freeze([Object.freeze({embed:true,path:bundledAssetPath(NebulaSans_Book_default2),style:"normal",weight:400}),Object.freeze({embed:true,path:bundledAssetPath(NebulaSans_Bold_default2),style:"normal",weight:700}),Object.freeze({embed:false,path:bundledAssetPath(NebulaSans_Book_default),style:"normal",weight:400}),Object.freeze({embed:false,path:bundledAssetPath(NebulaSans_Bold_default),style:"normal",weight:700})])});var escapeXml=(value)=>value.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">").replaceAll('"',""").replaceAll("'","'");function fontMime(filePath){switch(extname(filePath).toLowerCase()){case".otf":return"font/otf";case".woff":return"font/woff";case".woff2":return"font/woff2";default:return"font/ttf"}}async function fontCss(font){if(font.files===undefined)return"";const faces=await Promise.all(font.files.filter((file)=>file.embed===true).map(async(file)=>{const encoded=(await readFile(file.path)).toString("base64");return`@font-face{font-family:"${escapeXml(font.family)}";src:url(data:${fontMime(file.path)};base64,${encoded});font-weight:${file.weight??400};font-style:${file.style??"normal"};}`}));return faces.join("")}function textSvg(options){const lines=options.lines??wrapDiagramText(options.text,options.width,options.fontSize);const lineHeight=options.lineHeight??options.fontSize*1.25;const anchor=options.align;const x=anchor==="middle"?options.x+options.width/2:anchor==="end"?options.x+options.width:options.x;const dominantBaseline=options.centerLineBoxes?"central":"hanging";return`<text x="${x}" y="${options.y}" text-anchor="${anchor}" dominant-baseline="${dominantBaseline}" fill="${options.color}" opacity="${options.opacity}" font-family="${escapeXml(options.family)}" font-size="${options.fontSize}" font-weight="${options.weight}">${lines.map((line,index)=>options.centerLineBoxes?`<tspan x="${x}" y="${options.y+(index+0.5)*lineHeight}">${escapeXml(line)}</tspan>`:`<tspan x="${x}" dy="${index===0?0:lineHeight}">${escapeXml(line)}</tspan>`).join("")}</text>`}function iconSvg(options){const icon=sanitizeIcon(options.icon);const parts=icon.viewBox.trim().split(/\s+/).map(Number);const width=(parts[2]??24)-(parts[0]??0);const height=(parts[3]??24)-(parts[1]??0);const scale=options.size/Math.max(width,height);return`<g color="${options.color}" opacity="${options.opacity}" transform="translate(${options.x} ${options.y}) scale(${scale}) translate(${-Number(parts[0]??0)} ${-Number(parts[1]??0)})">${icon.body}</g>`}function boxSvg(shape,theme,families,icons){const tone=theme.tones[shape.tone??"neutral"];const strokeWidth=shape.strokeWidth??2;const opacity=shape.opacity??1;const radius=shape.type==="ellipse"?0:Math.min(shape.radius??22,shape.height/2);const geometry=shape.type==="ellipse"?`<ellipse cx="${shape.x+shape.width/2}" cy="${shape.y+shape.height/2}" rx="${shape.width/2}" ry="${shape.height/2}" fill="${shape.fill===false?"none":tone.fill}" stroke="${tone.stroke}" stroke-width="${strokeWidth}"/>`:`<rect x="${shape.x}" y="${shape.y}" width="${shape.width}" height="${shape.height}" rx="${radius}" fill="${shape.fill===false?"none":tone.fill}" stroke="${tone.stroke}" stroke-width="${strokeWidth}"/>`;const icon=shape.icon===undefined?undefined:icons[shape.icon];if(shape.icon!==undefined&&icon===undefined){throw new Error(`Unknown icon "${shape.icon}" on shape ${shape.id}`)}const iconSize=Math.min(shape.iconSize??52,shape.height*0.45,shape.width*0.32);const content=layoutBoxContent(shape,icon===undefined?undefined:iconSize);const iconMarkup=icon===undefined||content.icon===undefined?"":iconSvg({icon,x:content.icon.x,y:content.icon.y,size:content.icon.size,color:tone.text,opacity:1});const labelMarkup=content.rows.map((row)=>textSvg({text:row.text,x:shape.x+16,y:row.y,width:shape.width-32,fontSize:row.fontSize,weight:row.weight,align:"middle",color:tone.text,opacity:1,family:families[row.fontFamily],lineHeight:row.lineHeight,lines:row.lines,centerLineBoxes:true})).join("");return`<g data-shape-id="${escapeXml(shape.id)}" opacity="${opacity}">${geometry}${iconMarkup}${labelMarkup}</g>`}function pointForAnchor(shape,anchor,toward,position){const center={x:shape.x+shape.width/2,y:shape.y+shape.height/2};let resolved=anchor??"auto";if(resolved==="auto"){const dx=toward.x-center.x;const dy=toward.y-center.y;resolved=Math.abs(dx/shape.width)>=Math.abs(dy/shape.height)?dx>=0?"right":"left":dy>=0?"bottom":"top"}const resolvedPosition=position??0.5;if(shape.type==="ellipse"){const projected=(resolvedPosition-0.5)*2;const radial=Math.sqrt(Math.max(0,1-projected*projected))/2;const normalized=resolved==="top"?{x:resolvedPosition,y:0.5-radial}:resolved==="bottom"?{x:resolvedPosition,y:0.5+radial}:resolved==="left"?{x:0.5-radial,y:resolvedPosition}:{x:0.5+radial,y:resolvedPosition};return{x:shape.x+shape.width*normalized.x,y:shape.y+shape.height*normalized.y,normalized}}switch(resolved){case"top":return{x:shape.x+shape.width*resolvedPosition,y:shape.y,normalized:{x:resolvedPosition,y:0}};case"right":return{x:shape.x+shape.width,y:shape.y+shape.height*resolvedPosition,normalized:{x:1,y:resolvedPosition}};case"bottom":return{x:shape.x+shape.width*resolvedPosition,y:shape.y+shape.height,normalized:{x:resolvedPosition,y:1}};case"left":return{x:shape.x,y:shape.y+shape.height*resolvedPosition,normalized:{x:0,y:resolvedPosition}}}}function resolveEdge(spec,edge){const boxes=new Map(spec.shapes.filter((shape)=>shape.type==="rect"||shape.type==="ellipse").map((shape)=>[shape.id,shape]));const from=boxes.get(edge.from);const to=boxes.get(edge.to);if(from===undefined||to===undefined){throw new Error(`Edge ${edge.id} references a missing box`)}const fromCenter={x:from.x+from.width/2,y:from.y+from.height/2};const toCenter={x:to.x+to.width/2,y:to.y+to.height/2};const start=pointForAnchor(from,edge.start,toCenter,edge.startPosition);const end=pointForAnchor(to,edge.end,fromCenter,edge.endPosition);const dx=end.x-start.x;const dy=end.y-start.y;const length=Math.hypot(dx,dy)||1;const bend=edge.bend??0;return{edge,from,to,start,end,control:{x:(start.x+end.x)/2+-dy/length*bend,y:(start.y+end.y)/2+dx/length*bend}}}function resolveEdgeLabel(resolved){const{edge,start,end,control}=resolved;const t=edge.labelPosition??0.5;const oneMinusT=1-t;const point={x:oneMinusT*oneMinusT*start.x+2*oneMinusT*t*control.x+t*t*end.x,y:oneMinusT*oneMinusT*start.y+2*oneMinusT*t*control.y+t*t*end.y};const tangent={x:2*oneMinusT*(control.x-start.x)+2*t*(end.x-control.x),y:2*oneMinusT*(control.y-start.y)+2*t*(end.y-control.y)};const length=Math.hypot(tangent.x,tangent.y)||1;const offset=edge.labelOffset??-14;return{x:point.x+-tangent.y/length*offset,y:point.y+tangent.x/length*offset}}function edgeSvg(resolved,theme,families,strokeWidth){const{edge,start,end,control}=resolved;const toneName=edge.tone??"neutral";const tone=theme.tones[toneName];const marker=edge.arrowhead==="none"?"":edge.arrowhead==="triangle"?`url(#arrow-triangle-${toneName})`:`url(#arrow-open-${toneName})`;const path=`M ${start.x} ${start.y} Q ${control.x} ${control.y} ${end.x} ${end.y}`;const labelPoint=resolveEdgeLabel(resolved);const label=edge.label===undefined?"":`<text x="${labelPoint.x}" y="${labelPoint.y}" text-anchor="middle" dominant-baseline="central" fill="${tone.text}" stroke="${theme.background}" stroke-width="6" paint-order="stroke" font-family="${escapeXml(families[edge.labelFontFamily??"default"])}" font-size="${edge.labelFontSize??18}" font-weight="${edge.labelWeight??600}">${escapeXml(edge.label)}</text>`;return`<g data-edge-id="${escapeXml(edge.id)}"><path d="${path}" fill="none" stroke="${tone.stroke}" stroke-width="${strokeWidth}" stroke-linecap="round" marker-end="${marker}"/>${label}</g>`}function shapeSvg(shape,theme,families,icons){if(shape.type==="rect"||shape.type==="ellipse"){return boxSvg(shape,theme,families,icons)}if(shape.type==="line"){return`<line data-shape-id="${escapeXml(shape.id)}" x1="${shape.x}" y1="${shape.y}" x2="${shape.x2}" y2="${shape.y2}" stroke="${theme.tones[shape.tone??"neutral"].stroke}" stroke-width="${shape.strokeWidth??3}" stroke-linecap="round" opacity="${shape.opacity??1}"/>`}const text=shape;return textSvg({text:text.text,x:text.x,y:text.y,width:text.width??Math.max(text.text.length*(text.fontSize??24)*0.58,12),fontSize:text.fontSize??24,weight:text.weight??500,align:text.align??"start",color:theme.tones[text.tone??"neutral"].text,opacity:text.opacity??1,family:families[text.fontFamily??"default"]})}async function renderSvg(spec,mode,config,options={}){const edgeStrokeWidth=options.edgeStrokeWidth??3;if(!Number.isFinite(edgeStrokeWidth)||edgeStrokeWidth<0.5||edgeStrokeWidth>4)throw new Error("Edge stroke width must be between 0.5 and 4");const theme=resolveTheme(mode,config);const font=config.font??defaultFont;const families={default:font.family,mono:font.monoFamily??"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, monospace"};const icons=config.icons??{};const embeddedFonts=await fontCss(font);const markerDefinitions=Object.entries(theme.tones).map(([toneName,tone])=>`<marker id="arrow-open-${toneName}" markerWidth="12" markerHeight="12" refX="10" refY="6" orient="auto" markerUnits="strokeWidth"><path d="M2 2 10 6 2 10" fill="none" stroke="${tone.stroke}" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></marker><marker id="arrow-triangle-${toneName}" markerWidth="11" markerHeight="11" refX="9" refY="5.5" orient="auto" markerUnits="strokeWidth"><path d="M1 1 10 5.5 1 10z" fill="${tone.stroke}"/></marker>`).join("");const edgeMarkup=(spec.edges??[]).map((edge)=>edgeSvg(resolveEdge(spec,edge),theme,families,edgeStrokeWidth)).join("");const shapeMarkup=spec.shapes.map((shape)=>shapeSvg(shape,theme,families,icons)).join("");const svg=[`<svg xmlns="http://www.w3.org/2000/svg" width="${spec.canvas.width}" height="${spec.canvas.height}" viewBox="0 0 ${spec.canvas.width} ${spec.canvas.height}" role="img" aria-labelledby="diagram-title" color-scheme="${mode}">`,`<title id="diagram-title">${escapeXml(spec.name)}</title>`,`<style>${embeddedFonts}text{font-kerning:normal;text-rendering:geometricPrecision}</style>`,`<rect width="100%" height="100%" fill="${theme.background}"/>`,`<defs>${markerDefinitions}</defs>`,edgeMarkup,shapeMarkup,"</svg>"].join("");return{mode,svg,width:spec.canvas.width,height:spec.canvas.height}}function renderPng(rendered,config,scale=2){const font=config.font??defaultFont;const fontFiles=font.files?.map((file)=>file.path)??[];const renderer=new Resvg(rendered.svg,{fitTo:{mode:"zoom",value:scale},font:{loadSystemFonts:true,...fontFiles.length===0?{}:{fontFiles},defaultFontFamily:font.family}});return renderer.render().asPng()}function boxOutsideCanvas(shape,spec){return shape.x<0||shape.y<0||shape.x+shape.width>spec.canvas.width||shape.y+shape.height>spec.canvas.height}function lintDiagram(spec){const findings=[];const boxes=spec.shapes.filter((shape)=>shape.type==="rect"||shape.type==="ellipse");for(const shape of boxes){if(boxOutsideCanvas(shape,spec)){findings.push({code:"outside-canvas",message:`${shape.id} extends beyond the canvas`,shapeIds:[shape.id]})}const labels=shape.labelRows?.map((row)=>row.text)??(shape.label===undefined?[]:[shape.label]);const longLabel=labels.find((label)=>label.length>32);if(longLabel!==undefined){findings.push({code:"long-label",message:`${shape.id} has a ${longLabel.length}-character label; prefer a short noun phrase`,shapeIds:[shape.id]})}const iconSize=shape.icon===undefined?undefined:Math.min(shape.iconSize??52,shape.height*0.45,shape.width*0.32);const content=layoutBoxContent(shape,iconSize);const contentTop=content.icon?.y??content.rows[0]?.y;const finalRow=content.rows.at(-1);const contentBottom=finalRow===undefined?content.icon===undefined?undefined:content.icon.y+content.icon.size:finalRow.y+finalRow.height;const contentWidth=Math.max(1,shape.width-32);const exceedsWidth=content.rows.some((row)=>row.lines.some((line)=>estimateDiagramTextWidth(line,row.fontSize,row.fontFamily)>contentWidth));if(contentTop!==undefined&&contentBottom!==undefined&&(contentTop<shape.y||contentBottom>shape.y+shape.height||exceedsWidth)){findings.push({code:"label-overflow",message:`${shape.id} label and icon content exceeds the box bounds; increase the box or reduce authored sizes`,shapeIds:[shape.id]})}if(shape.width<120||shape.height<64){findings.push({code:"small-target",message:`${shape.id} is small enough to make its label or icon hard to scan`,shapeIds:[shape.id]})}}if(boxes.length>9){findings.push({code:"too-many-elements",message:`The diagram has ${boxes.length} primary shapes; consider a higher-level visual`,shapeIds:boxes.map((shape)=>shape.id)})}const sharedTerminals=new Map;for(const edge of spec.edges??[]){const resolved=resolveEdge(spec,edge);for(const terminal of[{kind:"start",point:resolved.start,shapeId:edge.from},{kind:"end",point:resolved.end,shapeId:edge.to}]){const key=`${terminal.kind}:${terminal.shapeId}:${terminal.point.x.toFixed(3)}:${terminal.point.y.toFixed(3)}`;const group=sharedTerminals.get(key)??[];group.push({edgeId:edge.id,shapeId:terminal.shapeId});sharedTerminals.set(key,group)}const length=Math.hypot(resolved.end.x-resolved.start.x,resolved.end.y-resolved.start.y);if(length<96){findings.push({code:"short-arrow",message:`${edge.id} is ${Math.round(length)}px long; leave more space between connected shapes`,shapeIds:[edge.from,edge.to]})}if(edge.label!==undefined&&edge.label.length>24){findings.push({code:"long-edge-label",message:`${edge.id} has a long connector label; prefer one short relation`,shapeIds:[edge.from,edge.to]})}}for(const group of sharedTerminals.values()){if(group.length<2)continue;findings.push({code:"shared-edge-port",message:`${group.map(({edgeId})=>edgeId).join(", ")} share one connector port; set distinct startPosition or endPosition values`,shapeIds:[group[0].shapeId]})}return findings}var diagramVersion=1;var stackLayoutDefaults={gap:160,padding:64,align:"center"};var coordinatePrecision=3;var coordinateScale=10**coordinatePrecision;class StackLayoutError extends Error{issues;constructor(issues){super(`Invalid stack layout:
|
|
4
4
|
${issues.map((issue)=>`- ${issue}`).join(`
|
|
5
5
|
`)}`);this.name="StackLayoutError";this.issues=issues}}function coordinateFromUnits(value){return value/coordinateScale}function normalizedScaledCoordinate(value){const scaled=value*coordinateScale;return normalizedGridValue(scaled)}function normalizedGridValue(value){const nearest=Math.round(value);return Math.abs(value-nearest)<=0.000000001?nearest:value}function floorGridValue(value){return Math.floor(normalizedGridValue(value))}function ceilGridValue(value){return Math.ceil(normalizedGridValue(value))}function coordinateUnits(value,label,positive,rounding,issues){if(!Number.isFinite(value)||(positive?value<=0:value<0)){issues.push(positive?`${label} must be positive`:`${label} must not be negative`);return 0}const scaled=normalizedScaledCoordinate(value);const units=rounding==="ceil"?ceilGridValue(scaled):floorGridValue(scaled);if(!Number.isSafeInteger(units)){issues.push(`${label} must be a finite coordinate within the supported range`);return 0}if(positive&&units<1){issues.push(`${label} must occupy at least ${coordinateFromUnits(1)}px in the layout grid`);return 0}return units}function positionedShape(shape,mainPosition,crossPosition,horizontal){return{...shape,x:coordinateFromUnits(horizontal?mainPosition:crossPosition),y:coordinateFromUnits(horizontal?crossPosition:mainPosition)}}function authoredMainSize(shape,horizontal){return horizontal?shape.width:shape.height}function emittedMainPlacement(shapes,horizontal,gap,issues){if(shapes.length===0)return{offsets:[],span:0};const offsets=[];let offset=0;for(const[index,shape]of shapes.entries()){offsets.push(offset);if(index===shapes.length-1)break;const advance=coordinateUnits(authoredMainSize(shape,horizontal)+gap,`space after shape ${shape.id}`,true,"ceil",issues);offset+=advance;if(!Number.isSafeInteger(offset)){issues.push("stack positions exceed the supported coordinate range");return{offsets,span:Number.POSITIVE_INFINITY}}}const last=shapes.at(-1);const span=offset+(last===undefined?0:normalizedScaledCoordinate(authoredMainSize(last,horizontal)));if(!Number.isFinite(span)||!Number.isSafeInteger(Math.ceil(span))){issues.push("stack extent exceeds the supported coordinate range")}return{offsets,span}}function clampedGridPosition(ideal,minimum,maximum){return Math.min(maximum,Math.max(minimum,Math.round(ideal)))}function resolvedAnchors(fromIndex,toIndex,horizontal){const forward=fromIndex<toIndex;if(horizontal){return forward?{start:"right",end:"left"}:{start:"left",end:"right"}}return forward?{start:"bottom",end:"top"}:{start:"top",end:"bottom"}}function resolvedEdge(edge,indexes,horizontal){const fromIndex=indexes.get(edge.from);const toIndex=indexes.get(edge.to);if(fromIndex===undefined||toIndex===undefined){throw new StackLayoutError([`edge ${edge.id} must reference shapes that belong to the stack`])}const anchors=resolvedAnchors(fromIndex,toIndex,horizontal);return{...edge,start:anchors.start,end:anchors.end,bend:0}}function stackStructureIssues(source){const issues=[];if(source.shapes.length<1||source.shapes.length>9){issues.push("stack layouts must contain between 1 and 9 shapes")}const indexes=new Map;const recordIds=new Set;for(const[index,shape]of source.shapes.entries()){if(recordIds.has(shape.id)){issues.push(`shape id ${shape.id} is duplicated`)}else{indexes.set(shape.id,index);recordIds.add(shape.id)}}const connectedPairs=new Set;for(const stackEdge of source.edges??[]){const edge=stackEdge;if(recordIds.has(edge.id)){issues.push(`edge id ${edge.id} is duplicated`)}recordIds.add(edge.id);const fromIndex=indexes.get(edge.from);const toIndex=indexes.get(edge.to);if(fromIndex===undefined){issues.push(`edge ${edge.id} has unknown from id ${edge.from}`)}if(toIndex===undefined){issues.push(`edge ${edge.id} has unknown to id ${edge.to}`)}if(fromIndex!==undefined&&toIndex!==undefined){if(fromIndex===toIndex){issues.push(`edge ${edge.id} cannot connect a shape to itself`)}else{if(Math.abs(fromIndex-toIndex)!==1){issues.push(`edge ${edge.id} must connect adjacent stack shapes`)}const pair=[fromIndex,toIndex].sort((left,right)=>left-right).join(":");if(connectedPairs.has(pair)){issues.push(`edge ${edge.id} duplicates a connection between the same stack shapes`)}connectedPairs.add(pair)}}if(edge.start!==undefined&&edge.start!=="auto"){issues.push(`edge ${edge.id}.start must be auto or omitted in a stack layout`)}if(edge.end!==undefined&&edge.end!=="auto"){issues.push(`edge ${edge.id}.end must be auto or omitted in a stack layout`)}if(edge.bend!==undefined&&edge.bend!==0){issues.push(`edge ${edge.id}.bend must be 0 or omitted in a stack layout`)}}return issues}function resolveStackLayout(source){const issues=[...stackStructureIssues(source)];const horizontal=source.layout.direction==="horizontal";coordinateUnits(source.canvas.width,"canvas width",true,"floor",issues);coordinateUnits(source.canvas.height,"canvas height",true,"floor",issues);const gapValue=source.layout.gap??stackLayoutDefaults.gap;coordinateUnits(gapValue,"stack gap",false,"ceil",issues);const paddingValue=source.canvas.padding??stackLayoutDefaults.padding;const padding=coordinateUnits(paddingValue,"canvas padding",false,"ceil",issues);for(const shape of source.shapes){coordinateUnits(shape.width,`shape ${shape.id} width`,true,"ceil",issues);coordinateUnits(shape.height,`shape ${shape.id} height`,true,"ceil",issues)}const align=source.layout.align??stackLayoutDefaults.align;const canvasMainValue=horizontal?source.canvas.width:source.canvas.height;const canvasCrossValue=horizontal?source.canvas.height:source.canvas.width;const mainPlacement=emittedMainPlacement(source.shapes,horizontal,gapValue,issues);const maximumMainStart=floorGridValue(normalizedScaledCoordinate(canvasMainValue-paddingValue)-mainPlacement.span);if(padding>maximumMainStart){const requiredMain=ceilGridValue(mainPlacement.span);const availableMain=floorGridValue(normalizedScaledCoordinate(canvasMainValue-paddingValue*2));issues.push(`${source.layout.direction} stack needs ${coordinateFromUnits(requiredMain)}px but only ${coordinateFromUnits(availableMain)}px remain inside ${paddingValue}px padding`)}const crossStarts=source.shapes.map((shape)=>{const authoredSize=horizontal?shape.height:shape.width;const maximum=floorGridValue(normalizedScaledCoordinate(canvasCrossValue-paddingValue-authoredSize));if(padding>maximum){const requiredCross=ceilGridValue(normalizedScaledCoordinate(authoredSize));const availableCross=floorGridValue(normalizedScaledCoordinate(canvasCrossValue-paddingValue*2));issues.push(`shape ${shape.id} needs ${coordinateFromUnits(requiredCross)}px on the cross axis but only ${coordinateFromUnits(availableCross)}px remain inside ${paddingValue}px padding`)}return{authoredSize,maximum}});if(issues.length>0)throw new StackLayoutError(issues);const mainStart=clampedGridPosition((normalizedScaledCoordinate(canvasMainValue)-mainPlacement.span)/2,padding,maximumMainStart);const shapes=source.shapes.map((shape,index)=>{const crossStart=crossStarts[index];if(crossStart===undefined){throw new StackLayoutError([`shape ${shape.id} has no cross-axis placement`])}const crossPosition=align==="start"?padding:align==="end"?crossStart.maximum:clampedGridPosition(normalizedScaledCoordinate((canvasCrossValue-crossStart.authoredSize)/2),padding,crossStart.maximum);const mainPosition=mainStart+(mainPlacement.offsets[index]??0);const positioned=positionedShape(shape,mainPosition,crossPosition,horizontal);return positioned});const indexes=new Map(shapes.map((shape,index)=>[shape.id,index]));const edges=source.edges?.map((edge)=>resolvedEdge(edge,indexes,horizontal));return{...source.$schema===undefined?{}:{$schema:source.$schema},version:source.version,name:source.name,canvas:{width:source.canvas.width,height:source.canvas.height,padding:paddingValue},shapes,...edges===undefined?{}:{edges}}}function resolveDiagramSource(source){return"layout"in source?resolveStackLayout(source):source}var tones2=new Set(["neutral","blue","orange","green","red","purple","yellow"]);var anchors=new Set(["auto","top","right","bottom","left"]);var fontFamilies=new Set(["default","mono"]);var stackDirections=new Set(["horizontal","vertical"]);var stackAlignments=new Set(["start","center","end"]);var idPattern=/^[A-Za-z0-9][A-Za-z0-9_-]*$/;var namePattern=/^[a-z0-9]+(?:-[a-z0-9]+)*$/;class DiagramValidationError extends Error{issues;constructor(issues){super(`Invalid diagram specification:
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import{__require}from"./index-80a36bc0.js";import{createHash,randomUUID}from"crypto";import{link,rm,writeFile}from"fs/promises";import{dirname,extname,resolve}from"path";class SlopcameraCloudError extends Error{code;constructor(code,message,options){super(`[${code}] ${message}`,options);this.name="SlopcameraCloudError";this.code=code}}var slopcameraImageModelPrices=Object.freeze({"openai/gpt-image-1":Object.freeze({kind:"tokens",textInputPerMillion:5000000,outputPerMillion:40000000,worstImage:250000,source:"https://developers.openai.com/api/docs/models/gpt-image-1"}),"openai/gpt-image-1-mini":Object.freeze({kind:"tokens",textInputPerMillion:2000000,outputPerMillion:8000000,worstImage:52000,source:"https://developers.openai.com/api/docs/models/gpt-image-1-mini"}),"openai/gpt-image-1.5":Object.freeze({kind:"tokens",textInputPerMillion:5000000,outputPerMillion:32000000,worstImage:200000,source:"https://developers.openai.com/api/docs/models/gpt-image-1.5"}),"recraft/recraft-v4.1-utility":Object.freeze({kind:"image",perImage:35000,source:"https://vercel.com/ai-gateway/models/recraft-v4.1-utility"})});var slopcameraPromptTokenAllowance=64;var slopcameraCostProvider="vercel-ai-gateway";function slopcameraImageModelPrice(model){return Object.hasOwn(slopcameraImageModelPrices,model)?slopcameraImageModelPrices[model]:undefined}function tokenCost(tokens,perMillion){return Math.ceil(tokens*perMillion/1e6)}function promptBytes(prompt){return Buffer.byteLength(prompt,"utf8")}function validTokenCount(value){return typeof value==="number"&&Number.isSafeInteger(value)&&value>=0}function estimatedLines(model,prompt,price){if(price.kind==="image"){return[{provider:slopcameraCostProvider,operation:`${model} image`,microUsd:price.perImage,basis:"estimated"}]}return[{provider:slopcameraCostProvider,operation:`${model} image output`,microUsd:price.worstImage,basis:"estimated"},{provider:slopcameraCostProvider,operation:`${model} text input`,microUsd:tokenCost(promptBytes(prompt)+slopcameraPromptTokenAllowance,price.textInputPerMillion),basis:"estimated"}]}function imageCallCosts(model,prompt,usage){const price=slopcameraImageModelPrice(model);if(price===undefined){return[{provider:slopcameraCostProvider,operation:`${model} image`,microUsd:0,basis:"unknown"}]}if(price.kind==="tokens"&&usage!==undefined&&validTokenCount(usage.inputTokens)&&validTokenCount(usage.outputTokens)&&usage.outputTokens>0){return[{provider:slopcameraCostProvider,operation:`${model} image output`,microUsd:tokenCost(usage.outputTokens,price.outputPerMillion),basis:"reported"},{provider:slopcameraCostProvider,operation:`${model} text input`,microUsd:tokenCost(usage.inputTokens,price.textInputPerMillion),basis:"reported"}]}return estimatedLines(model,prompt,price)}var slopcameraGatewayApiBaseUrl="https://ai-gateway.vercel.sh/v4/ai";var slopcameraImageModels=Object.freeze(["openai/gpt-image-1.5","recraft/recraft-v4.1-utility"]);var slopcameraResponseMediaTypes=Object.freeze(["image/png","image/jpeg","image/webp"]);var slopcameraMaximumPromptBytes=32*1024;var slopcameraMaximumRawImageBytes=64*1024*1024;var defaultGenerationTimeoutMs=5*60000;var maximumGenerationTimeoutMs=30*60000;var defaultMaximumGatewayResponseBytes=96*1024*1024;function invalidArgument(message){throw new SlopcameraCloudError("INVALID_ARGUMENT",message)}function credentialValue(value){if(value.length<16||value.length>16*1024||value.trim()!==value||/[^\x21-\x7e]/u.test(value)){throw new SlopcameraCloudError("AUTHENTICATION_REQUIRED","The selected Vercel AI Gateway credential is invalid.")}return value}function environment(injected){return injected??process.env}function resolveSlopcameraGatewayCredential(injected){const values=environment(injected);if(values.AI_GATEWAY_API_KEY!==undefined){return{source:"AI_GATEWAY_API_KEY",token:credentialValue(values.AI_GATEWAY_API_KEY)}}if(values.VERCEL_OIDC_TOKEN!==undefined){return{source:"VERCEL_OIDC_TOKEN",token:credentialValue(values.VERCEL_OIDC_TOKEN)}}throw new SlopcameraCloudError("AUTHENTICATION_REQUIRED","Set AI_GATEWAY_API_KEY or run Slopcamera through `vercel env run -- \u2026` with VERCEL_OIDC_TOKEN available.")}function slopcameraGatewayCredentialStatus(injected){const values=environment(injected);if(values.AI_GATEWAY_API_KEY!==undefined){credentialValue(values.AI_GATEWAY_API_KEY);return{available:true,source:"AI_GATEWAY_API_KEY"}}if(values.VERCEL_OIDC_TOKEN!==undefined){credentialValue(values.VERCEL_OIDC_TOKEN);return{available:true,source:"VERCEL_OIDC_TOKEN"}}return{available:false,source:null}}function validateModel(value){if(typeof value!=="string"||value.length<3||value.length>256||!/^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:-]*$/iu.test(value)){invalidArgument("Model must be a bounded Vercel AI Gateway provider/model id.")}return value}function validatePrompt(value){if(typeof value!=="string"||value.trim().length===0||/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/u.test(value)||Buffer.byteLength(value,"utf8")>slopcameraMaximumPromptBytes){invalidArgument(`Prompt must be non-empty and no more than ${slopcameraMaximumPromptBytes} UTF-8 bytes.`)}return value}function validateTimeout(value){const timeout=value??defaultGenerationTimeoutMs;if(!Number.isSafeInteger(timeout)||timeout<1000||timeout>maximumGenerationTimeoutMs){invalidArgument(`timeoutMs must be an integer from 1000 through ${maximumGenerationTimeoutMs}.`)}return timeout}function isObject(value){return typeof value==="object"&&value!==null||typeof value==="function"}function parseFunction(value){if(typeof value!=="function"){throw new SlopcameraCloudError("GENERATION_FAILED","The Vercel AI Gateway runtime is unavailable.")}return value}function disableAiSdkWarningLogging(){globalThis.AI_SDK_LOG_WARNINGS=false}function assertGenerationActive(signal){if(signal.aborted){throw new SlopcameraCloudError("GENERATION_FAILED","Vercel AI Gateway image generation was cancelled or exceeded its deadline.")}}async function loadDefaultGatewayRuntime(){let aiModule;let gatewayModule;try{[aiModule,gatewayModule]=await Promise.all([import("ai-v7"),import("@ai-sdk/gateway-v4")])}catch{throw new SlopcameraCloudError("GENERATION_FAILED","The Vercel AI Gateway runtime is unavailable.")}if(!isObject(aiModule)||!isObject(gatewayModule)){throw new SlopcameraCloudError("GENERATION_FAILED","The Vercel AI Gateway runtime is unavailable.")}const generateImage=parseFunction(aiModule.generateImage);if(typeof gatewayModule.createGateway!=="function"){throw new SlopcameraCloudError("GENERATION_FAILED","The Vercel AI Gateway runtime is unavailable.")}const createGateway=gatewayModule.createGateway;return{createGateway:(settings)=>{const provider=createGateway(settings);if(!isObject(provider)||typeof provider.imageModel!=="function"){throw new SlopcameraCloudError("GENERATION_FAILED","The Vercel AI Gateway runtime is unavailable.")}const imageModel=provider.imageModel;return{imageModel:(modelId)=>imageModel.call(provider,modelId)}},generateImage}}function gatewayUrl(input){try{if(input instanceof Request)return new URL(input.url);if(input instanceof URL)return new URL(input.href);return new URL(input,slopcameraGatewayApiBaseUrl)}catch{throw new SlopcameraCloudError("GENERATION_FAILED","The Vercel AI Gateway request was rejected.")}}function canonicalGatewayInput(input,url){if(!(input instanceof Request))return url;const snapshot=new Request(input);const body=snapshot.method==="GET"||snapshot.method==="HEAD"?undefined:snapshot.body??undefined;const requestInit={headers:snapshot.headers,method:snapshot.method,signal:snapshot.signal,...body===undefined?{}:{body,duplex:"half"}};return new Request(url.href,requestInit)}function boundedResponse(response,maximumBytes){const declared=response.headers.get("content-length");if(declared!==null){const value=Number(declared);if(!Number.isSafeInteger(value)||value<0||value>maximumBytes){response.body?.cancel().catch(()=>{return});throw new SlopcameraCloudError("GENERATION_INVALID_RESPONSE","Vercel AI Gateway returned an invalid bounded response.")}}if(response.body===null)return response;const reader=response.body.getReader();let bytes=0;const body=new ReadableStream({cancel:async(reason)=>await reader.cancel(reason),pull:async(controller)=>{try{const next=await reader.read();if(next.done){reader.releaseLock();controller.close();return}bytes+=next.value.byteLength;if(bytes>maximumBytes){await reader.cancel().catch(()=>{return});controller.error(new SlopcameraCloudError("GENERATION_INVALID_RESPONSE","Vercel AI Gateway returned an invalid bounded response."));return}controller.enqueue(next.value)}catch{controller.error(new SlopcameraCloudError("GENERATION_INVALID_RESPONSE","Vercel AI Gateway returned an invalid bounded response."))}}});return new Response(body,{headers:response.headers,status:response.status,statusText:response.statusText})}function createFixedGatewayFetch(options={}){const fetchImplementation=options.fetch??globalThis.fetch;const maximumResponseBytes=options.maximumResponseBytes??defaultMaximumGatewayResponseBytes;if(!Number.isSafeInteger(maximumResponseBytes)||maximumResponseBytes<1||maximumResponseBytes>1024*1024*1024){invalidArgument("maximumResponseBytes is outside the supported range.")}const fixed=new URL(slopcameraGatewayApiBaseUrl);return async(input,init)=>{const url=gatewayUrl(input);if(url.origin!==fixed.origin||url.pathname!==fixed.pathname&&!url.pathname.startsWith(`${fixed.pathname}/`)||url.username!==""||url.password!==""){throw new SlopcameraCloudError("GENERATION_FAILED","The Vercel AI Gateway request was rejected.")}const canonicalInput=canonicalGatewayInput(input,url);let response;try{response=await fetchImplementation(canonicalInput,{...init,redirect:"error"})}catch(error){if(error instanceof SlopcameraCloudError)throw error;throw new SlopcameraCloudError("GENERATION_FAILED","Vercel AI Gateway image generation failed; the request was not retried.")}if(response.redirected||response.status>=300&&response.status<400){await response.body?.cancel().catch(()=>{return});throw new SlopcameraCloudError("GENERATION_FAILED","The Vercel AI Gateway request was rejected.")}return boundedResponse(response,maximumResponseBytes)}}function combineSignals(caller,timeoutMs){const controller=new AbortController;const abort=()=>controller.abort(caller?.reason);let rejectInterruption;const interruption=new Promise((_resolve,reject)=>{rejectInterruption=reject});const rejectOnAbort=()=>{rejectInterruption?.(new SlopcameraCloudError("GENERATION_FAILED","Vercel AI Gateway image generation was cancelled or exceeded its deadline."))};controller.signal.addEventListener("abort",rejectOnAbort,{once:true});if(caller?.aborted===true)abort();caller?.addEventListener("abort",abort,{once:true});const timer=setTimeout(()=>controller.abort(),timeoutMs);return{dispose:()=>{clearTimeout(timer);caller?.removeEventListener("abort",abort);controller.signal.removeEventListener("abort",rejectOnAbort);rejectInterruption=undefined},interruption,signal:controller.signal}}function mediaType(value){if(typeof value!=="string"||!slopcameraResponseMediaTypes.includes(value)){throw new SlopcameraCloudError("GENERATION_INVALID_RESPONSE","Vercel AI Gateway returned an unsupported image type.")}return value}function validImageBytes(bytes,type){if(bytes.byteLength<12||bytes.byteLength>slopcameraMaximumRawImageBytes){return false}if(type==="image/png"){return Buffer.from(bytes.subarray(0,8)).equals(Buffer.from([137,80,78,71,13,10,26,10]))}if(type==="image/jpeg"){return bytes[0]===255&&bytes[1]===216&&bytes.at(-2)===255&&bytes.at(-1)===217}return bytes.byteLength>=16&&Buffer.from(bytes.subarray(0,4)).toString("ascii")==="RIFF"&&Buffer.from(bytes).readUInt32LE(4)===bytes.byteLength-8&&Buffer.from(bytes.subarray(8,12)).toString("ascii")==="WEBP"&&["VP8 ","VP8L","VP8X"].includes(Buffer.from(bytes.subarray(12,16)).toString("ascii"))}function warningReceipt(value){let type="provider-warning";let detail="Provider warning";if(typeof value==="string"){detail=value}else if(isObject(value)){if(typeof value.type==="string"&&value.type.length<=64){type=value.type.replace(/[^a-z0-9._-]/giu,"-")||type}if(typeof value.message==="string")detail=value.message;else if(typeof value.details==="string")detail=value.details}return`${type} sha256:${createHash("sha256").update(detail).digest("hex")}`}function returnedUsage(value){if(!isObject(value)||!isObject(value.usage))return;const{inputTokens,outputTokens}=value.usage;return{inputTokens:typeof inputTokens==="number"?inputTokens:undefined,outputTokens:typeof outputTokens==="number"?outputTokens:undefined}}function parseResult(value,model){if(!isObject(value)||!Array.isArray(value.images)||value.images.length!==1){throw new SlopcameraCloudError("GENERATION_INVALID_RESPONSE","Vercel AI Gateway did not return exactly one image.")}const image=value.images[0];if(!isObject(image)||!(image.uint8Array instanceof Uint8Array)){throw new SlopcameraCloudError("GENERATION_INVALID_RESPONSE","Vercel AI Gateway returned an invalid bounded image.")}const type=mediaType(image.mediaType);if(!validImageBytes(image.uint8Array,type)){throw new SlopcameraCloudError("GENERATION_INVALID_RESPONSE","Vercel AI Gateway returned an invalid bounded image.")}const gateway=isObject(value.providerMetadata)&&isObject(value.providerMetadata.gateway)?value.providerMetadata.gateway:undefined;const foreignGenerationId=gateway!==undefined&&typeof gateway.generationId==="string"&&gateway.generationId.length>0&&gateway.generationId.length<=256&&!/[\u0000-\u001f\u007f]/u.test(gateway.generationId)?gateway.generationId:randomUUID();const requestId=`sha256:${createHash("sha256").update("slopcamera.gateway-generation-id/v1\x00").update(foreignGenerationId).digest("hex")}`;const warnings=Array.isArray(value.warnings)?value.warnings.slice(0,100).map(warningReceipt):[];return{bytes:image.uint8Array,response:{image:{base64:Buffer.from(image.uint8Array).toString("base64"),mediaType:type},model,provider:"vercel-ai-gateway",requestId,warnings}}}async function performGeneration(input,dependencies){const model=validateModel(input.model);const prompt=validatePrompt(input.prompt);const credential=resolveSlopcameraGatewayCredential(dependencies.environment);const timeout=combineSignals(input.signal,validateTimeout(input.timeoutMs));let dispatched=false;let rejected=false;let reported=false;const report=(usage)=>{if(reported||!dispatched||rejected)return;reported=true;dependencies.onProviderCost?.(imageCallCosts(model,prompt,usage))};const observedFetch=async(request,init)=>{const response=await(dependencies.fetch??globalThis.fetch)(request,init);if(response.status>=400&&response.status<500)rejected=true;return response};try{const generation=(async()=>{assertGenerationActive(timeout.signal);disableAiSdkWarningLogging();const runtime=await(dependencies.loadRuntime??loadDefaultGatewayRuntime)();assertGenerationActive(timeout.signal);const provider=runtime.createGateway({apiKey:credential.token,baseURL:slopcameraGatewayApiBaseUrl,fetch:createFixedGatewayFetch({fetch:observedFetch,...dependencies.maximumResponseBytes===undefined?{}:{maximumResponseBytes:dependencies.maximumResponseBytes}})});assertGenerationActive(timeout.signal);dispatched=true;const generated=await runtime.generateImage({abortSignal:timeout.signal,maxRetries:0,model:provider.imageModel(model),n:1,prompt});report(returnedUsage(generated));return parseResult(generated,model)})();return await Promise.race([generation,timeout.interruption])}catch(error){report(undefined);if(error instanceof SlopcameraCloudError)throw error;throw new SlopcameraCloudError("GENERATION_FAILED","Vercel AI Gateway image generation failed; the request was not retried.")}finally{timeout.dispose()}}async function generateSlopcameraImage(input,dependencies={}){return(await performGeneration(input,dependencies)).response}function expectedMediaType(outputPath){const extension=extname(outputPath).toLocaleLowerCase("en-US");if(extension===".png")return"image/png";if(extension===".jpg"||extension===".jpeg")return"image/jpeg";if(extension===".webp")return"image/webp";invalidArgument("Output path must end in .png, .jpg, .jpeg, or .webp.")}async function atomicImageWrite(outputPath,bytes){const absolutePath=resolve(outputPath);const temporaryPath=resolve(dirname(absolutePath),`.${randomUUID()}.slopcamera-generate.tmp`);try{await writeFile(temporaryPath,bytes,{flag:"wx"});await link(temporaryPath,absolutePath);return absolutePath}catch{throw new SlopcameraCloudError("OUTPUT_WRITE_FAILED","Slopcamera could not atomically write the generated image.")}finally{await rm(temporaryPath,{force:true}).catch(()=>{return})}}async function generateSlopcameraImageFile(input,dependencies={}){if(typeof input.outputPath!=="string"||input.outputPath.length<1||input.outputPath.length>4096||input.outputPath.includes("\x00")){invalidArgument("Output path must be a non-empty local path.")}const expected=expectedMediaType(input.outputPath);const generated=await performGeneration(input,dependencies);if(generated.response.image.mediaType!==expected){throw new SlopcameraCloudError("GENERATION_INVALID_RESPONSE",`Generated ${generated.response.image.mediaType} does not match the requested ${expected} output path.`)}const outputPath=await atomicImageWrite(input.outputPath,generated.bytes);return{bytes:generated.bytes.byteLength,mediaType:generated.response.image.mediaType,model:generated.response.model,outputPath,provider:generated.response.provider,requestId:generated.response.requestId,sha256:createHash("sha256").update(generated.bytes).digest("hex"),warnings:generated.response.warnings}}
|
|
3
|
+
export{SlopcameraCloudError,slopcameraGatewayApiBaseUrl,slopcameraImageModels,slopcameraResponseMediaTypes,slopcameraMaximumPromptBytes,slopcameraMaximumRawImageBytes,resolveSlopcameraGatewayCredential,slopcameraGatewayCredentialStatus,createFixedGatewayFetch,generateSlopcameraImage,generateSlopcameraImageFile};
|