@hraness/slopcamera 3.8.0 → 3.9.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.
Files changed (35) hide show
  1. package/NOTICE.md +14 -0
  2. package/README.md +294 -298
  3. package/apps/desktop/application/html-overlay-integrity.ts +27 -4
  4. package/apps/desktop/application/operation.ts +1 -1
  5. package/apps/desktop/application/operations/spatial-render.ts +1 -1
  6. package/apps/desktop/cli/capability-manifest.ts +15 -2
  7. package/apps/desktop/cli/commands.ts +1 -1
  8. package/apps/desktop/cli/help.ts +23 -4
  9. package/apps/desktop/cli/portable-surface.ts +1 -0
  10. package/apps/desktop/cli/verbs.ts +4 -0
  11. package/apps/desktop/dist/cli/main.js +306 -284
  12. package/dist/cli.js +24 -4
  13. package/dist/{index-842t02pp.js → index-m6g0ca9b.js} +1 -1
  14. package/dist/{index-y8spxs77.js → index-mgychzh3.js} +6 -3
  15. package/dist/{index-k9bjw80z.js → index-rxx27q8b.js} +4 -4
  16. package/dist/index.js +1 -1
  17. package/dist/operations.js +1 -1
  18. package/dist/workflow.js +1 -1
  19. package/docs/README.md +4 -1
  20. package/package.json +5 -3
  21. package/skills/slopcamera/SKILL.md +2 -0
  22. package/skills/slopcamera/references/customization.md +8 -1
  23. package/skills/slopcamera/references/icon-place.md +76 -0
  24. package/skills/slopcamera/references/install.md +2 -2
  25. package/skills/slopcamera/references/music-video.md +10 -2
  26. package/skills/slopcamera/references/soundtracks.md +58 -0
  27. package/src/bounded-file.ts +135 -0
  28. package/src/cli.ts +191 -0
  29. package/src/icon-scene.ts +695 -0
  30. package/src/index.ts +2 -0
  31. package/src/mcp/tools.ts +311 -0
  32. package/src/mcp/types.ts +4 -0
  33. package/src/operations.ts +306 -0
  34. package/src/soundtrack.ts +489 -0
  35. package/src/version.ts +1 -1
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{DrawingValidationError,SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS,SLOPCAMERA_PORTABLE_CAPABILITY_VERSION,SLOPCAMERA_VERSION,STUDIO_LIMITS,SlopcameraCapabilityCommandSchema,SlopcameraCapabilityEffectSchema,SlopcameraCapabilityManifestSchema,SlopcameraCapabilityModuleDeclarationSchema,SlopcameraCapabilityModuleSchema,SlopcameraCapabilityOperationSchema,SlopcameraCapabilityPreparationSchema,SlopcameraCapabilityProfileSchema,SlopcameraCapabilityQualificationSchema,SlopcameraCapabilityResourceSchema,SlopcameraCapabilityResumeSchema,SlopcameraCapabilityToolSchema,SlopcameraCapabilityWorkflowSchema,SlopcameraMcpToolRuntime,SlopcameraSceneVariantsSchema,StudioCapabilityNameSchema,StudioDigestSchema,StudioEngineOptionsSchema,StudioEngineSchema,StudioExecutionProfileSchema,StudioJobSchema,StudioOutputArtifactSchema,StudioOutputFormatSchema,StudioOutputInterpretationSchema,StudioOutputRoleSchema,StudioOutputSpecSchema,StudioPathSchema,StudioPlanSchema,StudioReceiptSchema,StudioRenderSchema,StudioRuntimeIdentitySchema,StudioSourceBundleSchema,StudioSourceSpaceSchema,VISUAL_STYLE_IDS,VISUAL_STYLE_PROFILES,VISUAL_STYLE_VARIATION_ALGORITHM,VisualStyleIdSchema,VisualStyleProfileSchema,WorkspaceBoundary,WorkspaceBoundaryError,artifactSummary,checkDiagramFile,checkDrawingFile,compileSlopcameraCapabilityManifest,createPortableSlopcameraCapabilityManifest,createVisualStyleDirection,critiqueSlopcameraIconSet,defineSlopcameraCapabilityModule,diagramApi,drawingMinimumGlyphHeightPt,drawingPageGeometry,drawingProfile,drawingSourceByteLimit,generateSlopcameraIconSet,getVisualStyleProfile,inspectStudioBundle,inspectStudioPlan,mcpMaximumRenderedPixels,mcpMaximumScale,mcpSourceByteLimit,parseDrawingSource,parseSlopcameraCapabilityManifest,parseSlopcameraIconSetSpec,parseSlopcameraSceneVariants,parseStudioJob,parseStudioPlan,parseStudioReceipt,parseStudioRuntimeIdentity,parseStudioSourceBundle,planSlopcameraSceneGallery,planStudioJob,readDiagramFile,readDrawingFile,renderDiagramFile,renderDrawing,renderDrawingFile,runMcpServer,sampleVisualStyleExposure,slopcameraApi,slopcameraIconSetDefaultPool,slopcameraIconSetDefaultSetRounds,slopcameraIconSetMaximumMembers,slopcameraIconSetMaximumNameBytes,slopcameraIconSetMaximumSetRounds,slopcameraIconSetSlugPattern,slopcameraMcpProtocolVersion,slopcameraMcpServerName,slopcameraMcpTools,slopcameraPortableCapabilityManifest,slopcameraSceneGalleryLimits,starterDrawingSource,studioJobSha256,studioOutputPath,studioRuntimeSha256,studioSourceBundleSha256,summarizeSceneVariantPatch,validateStudioReceipt,visualStyleFrameVariation}from"./index-k9bjw80z.js";import{bundledSkillPath,installSkill}from"./index-grk47tgs.js";import"./index-z1kd1x8j.js";import"./index-3cxf2c58.js";import{SlopcameraWorkflowError,defineSlopcameraWorkflow,runSlopcameraWorkflow}from"./index-842t02pp.js";import{DiagramValidationError,SlopcameraOperationError,StackLayoutError,builtInIcons,collectSlopcameraIconCandidates,composeSlopcameraImageGallery,critiqueIconRaster,estimateIconStrokePixels,executeSlopcameraOperation,executeSlopcameraOperationWithLease,extractIconLineArt,galleryCandidateId,galleryPromptFor,generateSlopcameraIcon,generateSlopcameraImageGallery,iconPromptFor,iconVisualGateProblems,isSlopcameraOperationCode,lintDiagram,loadIconLanguageRuntime,measureIconCandidateMetrics,parseDiagramSource,parseDiagramSpec,parseSlopcameraGalleryVary,parseSlopcameraOperationInput,planSlopcameraGallery,renderIconPreview,renderPng,renderSvg,resolveDiagramSource,resolveEdge,resolveStackLayout,searchSlopcameraOperations,serializeTldr,slopcameraGalleryAxes,slopcameraGalleryKinds,slopcameraGalleryLimits,slopcameraIconContextProfiles,slopcameraIconContexts,slopcameraIconCritiqueDefaultModel,slopcameraIconCritiqueTimeoutMs,slopcameraIconDefaultInk,slopcameraIconDefaultRounds,slopcameraIconMaximumRounds,slopcameraIconPanel,slopcameraIconSubjectMaximumBytes,slopcameraOperationCodes,slopcameraOperationHostResourceClaims,slopcameraOperationRegistry,stackLayoutDefaults,withSlopcameraOperationHostAdmission,writeAtomically}from"./index-y8spxs77.js";import{VTRACER_VERSION,VectorizeError,vectorizeDefaultLimits,vectorizeHardLimits,vectorizeImage,vectorizeProfileNames,vtracerReleases}from"./index-mnp0bftq.js";import{SlopcameraCloudError,createFixedGatewayFetch,generateSlopcameraImage,generateSlopcameraImageFile,resolveSlopcameraGatewayCredential,slopcameraGatewayApiBaseUrl,slopcameraGatewayCredentialStatus,slopcameraImageModels,slopcameraMaximumPromptBytes,slopcameraMaximumRawImageBytes,slopcameraResponseMediaTypes}from"./index-p74xy76d.js";import{HOST_RESOURCE_MAX_WAIT_MILLISECONDS,HostResourceError,createDefaultHostResourceCoordinator,createHostResourceCoordinator,createProcessLocalHostResourceCoordinator,defaultSlopcameraHostResourceProfile,defaultSlopcameraHostResourceStateRoot,normalizeHostResourceClaims,normalizeHostResourceProfile,slopcameraHostResourceNames}from"./index-b3geqczn.js";import"./index-80a36bc0.js";export{writeAtomically,withSlopcameraOperationHostAdmission,vtracerReleases,visualStyleFrameVariation,vectorizeProfileNames,vectorizeImage,vectorizeHardLimits,vectorizeDefaultLimits,validateStudioReceipt,summarizeSceneVariantPatch,studioSourceBundleSha256,studioRuntimeSha256,studioOutputPath,studioJobSha256,starterDrawingSource,stackLayoutDefaults,slopcameraSceneGalleryLimits,slopcameraResponseMediaTypes,slopcameraPortableCapabilityManifest,slopcameraOperationRegistry,slopcameraOperationHostResourceClaims,slopcameraOperationCodes,slopcameraMcpTools,slopcameraMcpServerName,slopcameraMcpProtocolVersion,slopcameraMaximumRawImageBytes,slopcameraMaximumPromptBytes,slopcameraImageModels,slopcameraIconSubjectMaximumBytes,slopcameraIconSetSlugPattern,slopcameraIconSetMaximumSetRounds,slopcameraIconSetMaximumNameBytes,slopcameraIconSetMaximumMembers,slopcameraIconSetDefaultSetRounds,slopcameraIconSetDefaultPool,slopcameraIconPanel,slopcameraIconMaximumRounds,slopcameraIconDefaultRounds,slopcameraIconDefaultInk,slopcameraIconCritiqueTimeoutMs,slopcameraIconCritiqueDefaultModel,slopcameraIconContexts,slopcameraIconContextProfiles,slopcameraHostResourceNames,slopcameraGatewayCredentialStatus,slopcameraGatewayApiBaseUrl,slopcameraGalleryLimits,slopcameraGalleryKinds,slopcameraGalleryAxes,slopcameraApi,serializeTldr,searchSlopcameraOperations,sampleVisualStyleExposure,runSlopcameraWorkflow,runMcpServer,resolveStackLayout,resolveSlopcameraGatewayCredential,resolveEdge,resolveDiagramSource,renderSvg,renderPng,renderIconPreview,renderDrawingFile,renderDrawing,renderDiagramFile,readDrawingFile,readDiagramFile,planStudioJob,planSlopcameraSceneGallery,planSlopcameraGallery,parseStudioSourceBundle,parseStudioRuntimeIdentity,parseStudioReceipt,parseStudioPlan,parseStudioJob,parseSlopcameraSceneVariants,parseSlopcameraOperationInput,parseSlopcameraIconSetSpec,parseSlopcameraGalleryVary,parseSlopcameraCapabilityManifest,parseDrawingSource,parseDiagramSpec,parseDiagramSource,normalizeHostResourceProfile,normalizeHostResourceClaims,measureIconCandidateMetrics,mcpSourceByteLimit,mcpMaximumScale,mcpMaximumRenderedPixels,loadIconLanguageRuntime,lintDiagram,isSlopcameraOperationCode,installSkill,inspectStudioPlan,inspectStudioBundle,iconVisualGateProblems,iconPromptFor,getVisualStyleProfile,generateSlopcameraImageGallery,generateSlopcameraImageFile,generateSlopcameraImage,generateSlopcameraIconSet,generateSlopcameraIcon,galleryPromptFor,galleryCandidateId,extractIconLineArt,executeSlopcameraOperationWithLease,executeSlopcameraOperation,estimateIconStrokePixels,drawingSourceByteLimit,drawingProfile,drawingPageGeometry,drawingMinimumGlyphHeightPt,diagramApi,defineSlopcameraWorkflow,defineSlopcameraCapabilityModule,defaultSlopcameraHostResourceStateRoot,defaultSlopcameraHostResourceProfile,critiqueSlopcameraIconSet,critiqueIconRaster,createVisualStyleDirection,createProcessLocalHostResourceCoordinator,createPortableSlopcameraCapabilityManifest,createHostResourceCoordinator,createFixedGatewayFetch,createDefaultHostResourceCoordinator,composeSlopcameraImageGallery,compileSlopcameraCapabilityManifest,collectSlopcameraIconCandidates,checkDrawingFile,checkDiagramFile,bundledSkillPath,builtInIcons,artifactSummary,WorkspaceBoundaryError,WorkspaceBoundary,VisualStyleProfileSchema,VisualStyleIdSchema,VectorizeError,VTRACER_VERSION,VISUAL_STYLE_VARIATION_ALGORITHM,VISUAL_STYLE_PROFILES,VISUAL_STYLE_IDS,StudioSourceSpaceSchema,StudioSourceBundleSchema,StudioRuntimeIdentitySchema,StudioRenderSchema,StudioReceiptSchema,StudioPlanSchema,StudioPathSchema,StudioOutputSpecSchema,StudioOutputRoleSchema,StudioOutputInterpretationSchema,StudioOutputFormatSchema,StudioOutputArtifactSchema,StudioJobSchema,StudioExecutionProfileSchema,StudioEngineSchema,StudioEngineOptionsSchema,StudioDigestSchema,StudioCapabilityNameSchema,StackLayoutError,SlopcameraWorkflowError,SlopcameraSceneVariantsSchema,SlopcameraOperationError,SlopcameraMcpToolRuntime,SlopcameraCloudError,SlopcameraCapabilityWorkflowSchema,SlopcameraCapabilityToolSchema,SlopcameraCapabilityResumeSchema,SlopcameraCapabilityResourceSchema,SlopcameraCapabilityQualificationSchema,SlopcameraCapabilityProfileSchema,SlopcameraCapabilityPreparationSchema,SlopcameraCapabilityOperationSchema,SlopcameraCapabilityModuleSchema,SlopcameraCapabilityModuleDeclarationSchema,SlopcameraCapabilityManifestSchema,SlopcameraCapabilityEffectSchema,SlopcameraCapabilityCommandSchema,STUDIO_LIMITS,SLOPCAMERA_VERSION,SLOPCAMERA_PORTABLE_CAPABILITY_VERSION,SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS,HostResourceError,HOST_RESOURCE_MAX_WAIT_MILLISECONDS,DrawingValidationError,DiagramValidationError};
2
+ import{DrawingValidationError,SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS,SLOPCAMERA_PORTABLE_CAPABILITY_VERSION,SLOPCAMERA_VERSION,STUDIO_LIMITS,SlopcameraCapabilityCommandSchema,SlopcameraCapabilityEffectSchema,SlopcameraCapabilityManifestSchema,SlopcameraCapabilityModuleDeclarationSchema,SlopcameraCapabilityModuleSchema,SlopcameraCapabilityOperationSchema,SlopcameraCapabilityPreparationSchema,SlopcameraCapabilityProfileSchema,SlopcameraCapabilityQualificationSchema,SlopcameraCapabilityResourceSchema,SlopcameraCapabilityResumeSchema,SlopcameraCapabilityToolSchema,SlopcameraCapabilityWorkflowSchema,SlopcameraMcpToolRuntime,SlopcameraSceneVariantsSchema,StudioCapabilityNameSchema,StudioDigestSchema,StudioEngineOptionsSchema,StudioEngineSchema,StudioExecutionProfileSchema,StudioJobSchema,StudioOutputArtifactSchema,StudioOutputFormatSchema,StudioOutputInterpretationSchema,StudioOutputRoleSchema,StudioOutputSpecSchema,StudioPathSchema,StudioPlanSchema,StudioReceiptSchema,StudioRenderSchema,StudioRuntimeIdentitySchema,StudioSourceBundleSchema,StudioSourceSpaceSchema,VISUAL_STYLE_IDS,VISUAL_STYLE_PROFILES,VISUAL_STYLE_VARIATION_ALGORITHM,VisualStyleIdSchema,VisualStyleProfileSchema,WorkspaceBoundary,WorkspaceBoundaryError,artifactSummary,checkDiagramFile,checkDrawingFile,compileSlopcameraCapabilityManifest,createPortableSlopcameraCapabilityManifest,createVisualStyleDirection,critiqueSlopcameraIconSet,defineSlopcameraCapabilityModule,diagramApi,drawingMinimumGlyphHeightPt,drawingPageGeometry,drawingProfile,drawingSourceByteLimit,generateSlopcameraIconSet,getVisualStyleProfile,inspectStudioBundle,inspectStudioPlan,mcpMaximumRenderedPixels,mcpMaximumScale,mcpSourceByteLimit,parseDrawingSource,parseSlopcameraCapabilityManifest,parseSlopcameraIconSetSpec,parseSlopcameraSceneVariants,parseStudioJob,parseStudioPlan,parseStudioReceipt,parseStudioRuntimeIdentity,parseStudioSourceBundle,planSlopcameraSceneGallery,planStudioJob,readDiagramFile,readDrawingFile,renderDiagramFile,renderDrawing,renderDrawingFile,runMcpServer,sampleVisualStyleExposure,slopcameraApi,slopcameraIconSetDefaultPool,slopcameraIconSetDefaultSetRounds,slopcameraIconSetMaximumMembers,slopcameraIconSetMaximumNameBytes,slopcameraIconSetMaximumSetRounds,slopcameraIconSetSlugPattern,slopcameraMcpProtocolVersion,slopcameraMcpServerName,slopcameraMcpTools,slopcameraPortableCapabilityManifest,slopcameraSceneGalleryLimits,starterDrawingSource,studioJobSha256,studioOutputPath,studioRuntimeSha256,studioSourceBundleSha256,summarizeSceneVariantPatch,validateStudioReceipt,visualStyleFrameVariation}from"./index-rxx27q8b.js";import{bundledSkillPath,installSkill}from"./index-grk47tgs.js";import"./index-z1kd1x8j.js";import"./index-3cxf2c58.js";import{SlopcameraWorkflowError,defineSlopcameraWorkflow,runSlopcameraWorkflow}from"./index-m6g0ca9b.js";import{DiagramValidationError,SlopcameraIconError,SlopcameraOperationError,SlopcameraSoundtrackError,StackLayoutError,assertInertIconSvg,builtInIcons,collectSlopcameraIconCandidates,composeSlopcameraIcon,composeSlopcameraImageGallery,composeSlopcameraSoundtrack,critiqueIconRaster,deriveSlopcameraSoundtrackGrid,detectSlopcameraSoundtrackFormat,estimateIconStrokePixels,executeSlopcameraOperation,executeSlopcameraOperationWithLease,extractIconLineArt,galleryCandidateId,galleryPromptFor,generateSlopcameraIcon,generateSlopcameraImageGallery,iconPromptFor,iconVisualGateProblems,isSlopcameraOperationCode,lintDiagram,loadIconLanguageRuntime,measureIconCandidateMetrics,parseDiagramSource,parseDiagramSpec,parseSlopcameraGalleryVary,parseSlopcameraOperationInput,planSlopcameraGallery,projectSlopcameraSoundtrackGrid,renderIconPreview,renderPng,renderSlopcameraIcon,renderSvg,resolveDiagramSource,resolveEdge,resolveStackLayout,searchSlopcameraOperations,serializeTldr,slopcameraGalleryAxes,slopcameraGalleryKinds,slopcameraGalleryLimits,slopcameraIconContextProfiles,slopcameraIconContexts,slopcameraIconCritiqueDefaultModel,slopcameraIconCritiqueTimeoutMs,slopcameraIconDefaultInk,slopcameraIconDefaultLanguage,slopcameraIconDefaultRounds,slopcameraIconEngine,slopcameraIconLanguages,slopcameraIconMaximumRounds,slopcameraIconPalettes,slopcameraIconPanel,slopcameraIconSceneLimits,slopcameraIconSubjectMaximumBytes,slopcameraOperationCodes,slopcameraOperationHostResourceClaims,slopcameraOperationRegistry,slopcameraSoundtrackEngine,slopcameraSoundtrackFormats,slopcameraSoundtrackLimits,soundtrackBeatTimeUs,stackLayoutDefaults,withSlopcameraOperationHostAdmission,writeAtomically}from"./index-mgychzh3.js";import{VTRACER_VERSION,VectorizeError,vectorizeDefaultLimits,vectorizeHardLimits,vectorizeImage,vectorizeProfileNames,vtracerReleases}from"./index-mnp0bftq.js";import{SlopcameraCloudError,createFixedGatewayFetch,generateSlopcameraImage,generateSlopcameraImageFile,resolveSlopcameraGatewayCredential,slopcameraGatewayApiBaseUrl,slopcameraGatewayCredentialStatus,slopcameraImageModels,slopcameraMaximumPromptBytes,slopcameraMaximumRawImageBytes,slopcameraResponseMediaTypes}from"./index-p74xy76d.js";import{HOST_RESOURCE_MAX_WAIT_MILLISECONDS,HostResourceError,createDefaultHostResourceCoordinator,createHostResourceCoordinator,createProcessLocalHostResourceCoordinator,defaultSlopcameraHostResourceProfile,defaultSlopcameraHostResourceStateRoot,normalizeHostResourceClaims,normalizeHostResourceProfile,slopcameraHostResourceNames}from"./index-b3geqczn.js";import"./index-80a36bc0.js";export{writeAtomically,withSlopcameraOperationHostAdmission,vtracerReleases,visualStyleFrameVariation,vectorizeProfileNames,vectorizeImage,vectorizeHardLimits,vectorizeDefaultLimits,validateStudioReceipt,summarizeSceneVariantPatch,studioSourceBundleSha256,studioRuntimeSha256,studioOutputPath,studioJobSha256,starterDrawingSource,stackLayoutDefaults,soundtrackBeatTimeUs,slopcameraSoundtrackLimits,slopcameraSoundtrackFormats,slopcameraSoundtrackEngine,slopcameraSceneGalleryLimits,slopcameraResponseMediaTypes,slopcameraPortableCapabilityManifest,slopcameraOperationRegistry,slopcameraOperationHostResourceClaims,slopcameraOperationCodes,slopcameraMcpTools,slopcameraMcpServerName,slopcameraMcpProtocolVersion,slopcameraMaximumRawImageBytes,slopcameraMaximumPromptBytes,slopcameraImageModels,slopcameraIconSubjectMaximumBytes,slopcameraIconSetSlugPattern,slopcameraIconSetMaximumSetRounds,slopcameraIconSetMaximumNameBytes,slopcameraIconSetMaximumMembers,slopcameraIconSetDefaultSetRounds,slopcameraIconSetDefaultPool,slopcameraIconSceneLimits,slopcameraIconPanel,slopcameraIconPalettes,slopcameraIconMaximumRounds,slopcameraIconLanguages,slopcameraIconEngine,slopcameraIconDefaultRounds,slopcameraIconDefaultLanguage,slopcameraIconDefaultInk,slopcameraIconCritiqueTimeoutMs,slopcameraIconCritiqueDefaultModel,slopcameraIconContexts,slopcameraIconContextProfiles,slopcameraHostResourceNames,slopcameraGatewayCredentialStatus,slopcameraGatewayApiBaseUrl,slopcameraGalleryLimits,slopcameraGalleryKinds,slopcameraGalleryAxes,slopcameraApi,serializeTldr,searchSlopcameraOperations,sampleVisualStyleExposure,runSlopcameraWorkflow,runMcpServer,resolveStackLayout,resolveSlopcameraGatewayCredential,resolveEdge,resolveDiagramSource,renderSvg,renderSlopcameraIcon,renderPng,renderIconPreview,renderDrawingFile,renderDrawing,renderDiagramFile,readDrawingFile,readDiagramFile,projectSlopcameraSoundtrackGrid,planStudioJob,planSlopcameraSceneGallery,planSlopcameraGallery,parseStudioSourceBundle,parseStudioRuntimeIdentity,parseStudioReceipt,parseStudioPlan,parseStudioJob,parseSlopcameraSceneVariants,parseSlopcameraOperationInput,parseSlopcameraIconSetSpec,parseSlopcameraGalleryVary,parseSlopcameraCapabilityManifest,parseDrawingSource,parseDiagramSpec,parseDiagramSource,normalizeHostResourceProfile,normalizeHostResourceClaims,measureIconCandidateMetrics,mcpSourceByteLimit,mcpMaximumScale,mcpMaximumRenderedPixels,loadIconLanguageRuntime,lintDiagram,isSlopcameraOperationCode,installSkill,inspectStudioPlan,inspectStudioBundle,iconVisualGateProblems,iconPromptFor,getVisualStyleProfile,generateSlopcameraImageGallery,generateSlopcameraImageFile,generateSlopcameraImage,generateSlopcameraIconSet,generateSlopcameraIcon,galleryPromptFor,galleryCandidateId,extractIconLineArt,executeSlopcameraOperationWithLease,executeSlopcameraOperation,estimateIconStrokePixels,drawingSourceByteLimit,drawingProfile,drawingPageGeometry,drawingMinimumGlyphHeightPt,diagramApi,detectSlopcameraSoundtrackFormat,deriveSlopcameraSoundtrackGrid,defineSlopcameraWorkflow,defineSlopcameraCapabilityModule,defaultSlopcameraHostResourceStateRoot,defaultSlopcameraHostResourceProfile,critiqueSlopcameraIconSet,critiqueIconRaster,createVisualStyleDirection,createProcessLocalHostResourceCoordinator,createPortableSlopcameraCapabilityManifest,createHostResourceCoordinator,createFixedGatewayFetch,createDefaultHostResourceCoordinator,composeSlopcameraSoundtrack,composeSlopcameraImageGallery,composeSlopcameraIcon,compileSlopcameraCapabilityManifest,collectSlopcameraIconCandidates,checkDrawingFile,checkDiagramFile,bundledSkillPath,builtInIcons,assertInertIconSvg,artifactSummary,WorkspaceBoundaryError,WorkspaceBoundary,VisualStyleProfileSchema,VisualStyleIdSchema,VectorizeError,VTRACER_VERSION,VISUAL_STYLE_VARIATION_ALGORITHM,VISUAL_STYLE_PROFILES,VISUAL_STYLE_IDS,StudioSourceSpaceSchema,StudioSourceBundleSchema,StudioRuntimeIdentitySchema,StudioRenderSchema,StudioReceiptSchema,StudioPlanSchema,StudioPathSchema,StudioOutputSpecSchema,StudioOutputRoleSchema,StudioOutputInterpretationSchema,StudioOutputFormatSchema,StudioOutputArtifactSchema,StudioJobSchema,StudioExecutionProfileSchema,StudioEngineSchema,StudioEngineOptionsSchema,StudioDigestSchema,StudioCapabilityNameSchema,StackLayoutError,SlopcameraWorkflowError,SlopcameraSoundtrackError,SlopcameraSceneVariantsSchema,SlopcameraOperationError,SlopcameraMcpToolRuntime,SlopcameraIconError,SlopcameraCloudError,SlopcameraCapabilityWorkflowSchema,SlopcameraCapabilityToolSchema,SlopcameraCapabilityResumeSchema,SlopcameraCapabilityResourceSchema,SlopcameraCapabilityQualificationSchema,SlopcameraCapabilityProfileSchema,SlopcameraCapabilityPreparationSchema,SlopcameraCapabilityOperationSchema,SlopcameraCapabilityModuleSchema,SlopcameraCapabilityModuleDeclarationSchema,SlopcameraCapabilityManifestSchema,SlopcameraCapabilityEffectSchema,SlopcameraCapabilityCommandSchema,STUDIO_LIMITS,SLOPCAMERA_VERSION,SLOPCAMERA_PORTABLE_CAPABILITY_VERSION,SLOPCAMERA_CAPABILITY_MANIFEST_LIMITS,HostResourceError,HOST_RESOURCE_MAX_WAIT_MILLISECONDS,DrawingValidationError,DiagramValidationError};
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{SlopcameraOperationError,executeSlopcameraOperation,executeSlopcameraOperationWithLease,isSlopcameraOperationCode,parseSlopcameraOperationInput,searchSlopcameraOperations,slopcameraOperationCodes,slopcameraOperationHostResourceClaims,slopcameraOperationRegistry,withSlopcameraOperationHostAdmission}from"./index-y8spxs77.js";import"./index-mnp0bftq.js";import"./index-p74xy76d.js";import"./index-b3geqczn.js";import"./index-80a36bc0.js";export{withSlopcameraOperationHostAdmission,slopcameraOperationRegistry,slopcameraOperationHostResourceClaims,slopcameraOperationCodes,searchSlopcameraOperations,parseSlopcameraOperationInput,isSlopcameraOperationCode,executeSlopcameraOperationWithLease,executeSlopcameraOperation,SlopcameraOperationError};
2
+ import{SlopcameraOperationError,executeSlopcameraOperation,executeSlopcameraOperationWithLease,isSlopcameraOperationCode,parseSlopcameraOperationInput,searchSlopcameraOperations,slopcameraOperationCodes,slopcameraOperationHostResourceClaims,slopcameraOperationRegistry,withSlopcameraOperationHostAdmission}from"./index-mgychzh3.js";import"./index-mnp0bftq.js";import"./index-p74xy76d.js";import"./index-b3geqczn.js";import"./index-80a36bc0.js";export{withSlopcameraOperationHostAdmission,slopcameraOperationRegistry,slopcameraOperationHostResourceClaims,slopcameraOperationCodes,searchSlopcameraOperations,parseSlopcameraOperationInput,isSlopcameraOperationCode,executeSlopcameraOperationWithLease,executeSlopcameraOperation,SlopcameraOperationError};
package/dist/workflow.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- import{SlopcameraWorkflowError,defineSlopcameraWorkflow,runSlopcameraWorkflow}from"./index-842t02pp.js";import"./index-y8spxs77.js";import"./index-mnp0bftq.js";import"./index-p74xy76d.js";import"./index-b3geqczn.js";import"./index-80a36bc0.js";export{runSlopcameraWorkflow,defineSlopcameraWorkflow,SlopcameraWorkflowError};
2
+ import{SlopcameraWorkflowError,defineSlopcameraWorkflow,runSlopcameraWorkflow}from"./index-m6g0ca9b.js";import"./index-mgychzh3.js";import"./index-mnp0bftq.js";import"./index-p74xy76d.js";import"./index-b3geqczn.js";import"./index-80a36bc0.js";export{runSlopcameraWorkflow,defineSlopcameraWorkflow,SlopcameraWorkflowError};
package/docs/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SlopCamera documentation
2
2
 
3
- Slopcamera is a media studio for coding agents. It retains editable source, renders local or generated media, and assembles it into inspectable video projects. Choose a guide for the work you want to do.
3
+ Slopcamera is a media studio for coding agents. Your agent writes a short source file, the CLI checks and renders it, and a revision is an edit to that file and a new render. Choose a guide for the work you want to do.
4
4
 
5
5
  Install the [current Slopcamera release](../README.md#install-slopcamera) for the CLI, SDK, and matching Agent Skill. Each guide names its local runtimes and any correction that needs [current source](how-to/use-current-source.md). Some examples import TypeScript from the repository and need a checkout to run. Historical Atet archives retain their original package and commands. The [CLI parity table](cli-parity.md) maps every action of the retired menu-bar companion to a command. The [capability reference](reference/capabilities.md) distinguishes them and lists local runtime requirements.
6
6
 
@@ -35,12 +35,15 @@ Install the [current Slopcamera release](../README.md#install-slopcamera) for th
35
35
 
36
36
  - [Capabilities, versions, and platforms](reference/capabilities.md): current Slopcamera capabilities, historical Atet versions, and supported runtime boundaries.
37
37
  - [SDK surfaces](reference/sdk.md): portable and local imports, operation projections, and execution contracts.
38
+ - [Techniques catalog](https://slopcamera.com/docs/reference/techniques): every packaged technique by job, with its first command, guide, and a rendered example where one exists.
38
39
  - [CLI help](reference/capabilities.md#discover-the-installed-contract): exact grammar and JSON schemas from the installed host.
39
40
 
40
41
  ## Understand the design
41
42
 
42
43
  - [Source, representations, and projects](architecture.md): what stays editable, what a receipt proves, and how local and cloud work fit together.
43
44
  - [Extension architecture](extension-architecture.md): the closed registry, inert recipe packs, trusted workflows, and where authored code can and cannot go.
45
+ - [Why Slopcamera](https://slopcamera.com/docs/explanation/why-slopcamera): why the agent keeps a source file and reuses installed techniques, with comparisons to [Remotion](https://slopcamera.com/docs/explanation/slopcamera-vs-remotion), [HyperFrames](https://slopcamera.com/docs/explanation/slopcamera-vs-hyperframes), and [other tools](https://slopcamera.com/docs/explanation/remotion-alternatives-for-coding-agents).
46
+ - [Token benchmark](https://slopcamera.com/docs/explanation/token-benchmark): agent token use and cost on four media tasks with and without Slopcamera installed, with the harness and raw results in [`bench/token-savings`](../bench/token-savings/README.md).
44
47
  - [Choose an HTML authoring surface](html-overlay-creative-toolkit.md): why DOM, vector, Three.js, and explicit GPU profiles serve different jobs. Its ecosystem research is dated separately from its supported locks.
45
48
 
46
49
  ## Work with an agent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hraness/slopcamera",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "description": "SlopCamera lets your coding agent make images, diagrams, animation, 3D scenes, and video from source files it can keep revising.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -220,7 +220,7 @@
220
220
  "typecheck:sdk": "tsc --noEmit -p tsconfig.json",
221
221
  "typecheck:desktop": "tsc --noEmit -p apps/desktop/tsconfig.json",
222
222
  "lint": "bun run lint:sdk && bun run lint:desktop && bun run lint:api",
223
- "lint:sdk": "eslint src packages/scene scripts",
223
+ "lint:sdk": "eslint src packages/scene scripts bench",
224
224
  "lint:desktop": "eslint apps/desktop/contracts apps/desktop/core apps/desktop/application apps/desktop/code apps/desktop/workflows apps/desktop/html-overlay apps/desktop/cli apps/desktop/qualification apps/desktop/analysis apps/desktop/studio",
225
225
  "test": "bun run test:sdk && bun run test:desktop && bun run test:api && bun run --cwd apps/web test",
226
226
  "test:sdk": "bun test ./src ./packages/scene",
@@ -242,7 +242,7 @@
242
242
  "test:html-overlay:operation:macos": "SLOPCAMERA_RUN_HTML_OVERLAY_OPERATION_SMOKE=1 bun test ./apps/desktop/application/operations/media/html-overlay.test.ts",
243
243
  "verify:html-overlay:macos": "bun run test:html-overlay:browser:macos && bun run test:html-overlay:libraries:macos && bun run test:html-overlay:operation:macos",
244
244
  "benchmark:code-concurrency": "bun run ./apps/desktop/code/concurrency-benchmark.ts",
245
- "check:release-workflows": "bun test ./scripts/release-workflow.test.ts ./scripts/github-release.test.ts",
245
+ "check:release-workflows": "bun test ./scripts/release-workflow.test.ts ./scripts/github-release.test.ts ./scripts/dependency-admissions.test.ts",
246
246
  "check:schema": "bun run ./scripts/validate-schema.ts",
247
247
  "check:skill": "bun run ./scripts/validate-skill.ts && bun test ./scripts/social-collage-compositor.test.ts && bun examples/style-portfolio/verify-animation-source.mjs",
248
248
  "check:standalone": "bun test ./scripts/standalone-support-fixture.test.ts && bun run ./scripts/check-standalone.ts",
@@ -273,6 +273,8 @@
273
273
  "@ai-sdk/gateway-v4": "npm:@ai-sdk/gateway@^4.0.27",
274
274
  "@hraness/algal": "github:hraness/algal#c46264a",
275
275
  "@hraness/desktop-foundation": "https://github.com/hraness/desktop-foundation/releases/download/v0.9.0/hraness-desktop-foundation-0.9.0.tgz",
276
+ "@hraness/iconplace": "0.1.0",
277
+ "@hraness/soundfish": "0.7.0",
276
278
  "@hraness/support-foundation": "github:hraness/support-foundation#8bb514d24b79dc3f305390700ae312cab88e7ad2",
277
279
  "@noble/hashes": "2.2.0",
278
280
  "@paper-design/shaders": "0.0.77",
@@ -30,6 +30,8 @@ Slopcamera installs from its verified release archive or from source. Historical
30
30
  | Patent-style drawing sheets, physical margins, monochrome SVGs or a drawing PDF | [Drawing sheets](references/patent-drawings.md) |
31
31
  | Custom diagram themes, fonts or icon packages | [Customization](references/customization.md) |
32
32
  | Raster artwork converted to SVG | [Vectorization](references/vectorization.md) |
33
+ | Deterministic vector icon, icon scene or construction program, no model | [Icon scenes](references/icon-place.md) |
34
+ | Tempo, meter and section cues from a Soundfish score or MIDI file | [Soundtracks](references/soundtracks.md) |
33
35
  | Clean up, caption, frame, or deliver existing recordings and footage | [Video projects](references/video-projects.md) |
34
36
  | Music video, dancing mascot or timed HTML scene with a local soundtrack | [Music videos](references/music-video.md) |
35
37
  | Product launch film, UI mockup motion, kinetic type, or coming from HyperFrames or Remotion | [Launch films](references/launch-films.md); for a whole Hraness product launch, the `product-launch` agent skill |
@@ -1,6 +1,7 @@
1
1
  # Fonts and icon adapters
2
2
 
3
- `slopcamera` deliberately ships no commercial font and no large icon set.
3
+ `slopcamera` deliberately ships no commercial font and no large icon set. Its
4
+ one built-in icon library, icon.place, is described below.
4
5
 
5
6
  ## Custom font
6
7
 
@@ -72,6 +73,12 @@ package's data into `{ viewBox, body }`. Keep that package in the consuming
72
73
  repository rather than adding it to `slopcamera`. Preserve the icon package's
73
74
  license and attribution requirements.
74
75
 
76
+ icon.place is the one icon library built into `slopcamera`. It draws its own
77
+ icon scenes and construction programs to standalone SVG through
78
+ `slopcamera image icon compose|render`; see [icon scenes](icon-place.md). It
79
+ does not add icons to the diagram `icons` table, so diagram icons from any
80
+ other package still go through a local adapter.
81
+
75
82
  Icon bodies may contain ordinary SVG geometry such as `path`, `circle`, `rect`,
76
83
  `line`, `polyline`, and `polygon`. Scripts, event handlers, `foreignObject`, and
77
84
  embedded web content are rejected.
@@ -0,0 +1,76 @@
1
+ # Compose and render vector icon scenes
2
+
3
+ Use this workflow when the user wants a deterministic vector icon, a small
4
+ composed scene of icons, or a drawn construction program, without a model or a
5
+ network request. Slopcamera bundles the icon.place library at one exact version
6
+ for this. For a generated product mark or topic icon from a text brief, use
7
+ [brand illustrations](brand-illustrations.md) instead.
8
+
9
+ ## Pick a source
10
+
11
+ Both commands read one local JSON file of at most 1 MiB. The kind is detected
12
+ from its content:
13
+
14
+ - A **scene**: up to eight placed icons with a canvas and a background.
15
+ - A **collection**: up to six layered icons that compose into one scene.
16
+ - A **construction program**: parts, operations and a drawing recipe that
17
+ compile to one drawing.
18
+ - A **recipe** that an earlier render wrote. A recipe records the source, the
19
+ render settings and the output digests.
20
+
21
+ Keep the source in the workspace as the authoritative file. The SVG is a
22
+ replaceable derivative.
23
+
24
+ ## Compose
25
+
26
+ `compose` parses and solves the source and reports its digests, element count
27
+ and diagnostics. It writes nothing unless `--output` names a `.json` file for the
28
+ canonical solved document:
29
+
30
+ ```sh
31
+ slopcamera image icon compose icons/window.json --json
32
+ slopcamera image icon compose icons/window.json --output icons/window.solved.json
33
+ ```
34
+
35
+ Composing a solved document again gives the same digests. Report any
36
+ diagnostics; they are the library's own layout findings.
37
+
38
+ ## Render
39
+
40
+ `render` draws the source to an inert SVG. `--recipe` also writes a replayable
41
+ recipe:
42
+
43
+ ```sh
44
+ slopcamera image icon render icons/window.json --output icons/window.svg \
45
+ --recipe icons/window.recipe.json --size 256 --palette ink
46
+ ```
47
+
48
+ - `--size` sets the output edge from 16 to 1024 pixels.
49
+ - `--palette` (`original`, `ink`, `earth` or `night`) applies to scenes and
50
+ collections. `--background` paints the paper behind any source.
51
+ - `--language` selects one of the drawing languages for a construction program,
52
+ such as `outline`, `woodcut` or `stipple`. `slopcamera help image` lists them.
53
+
54
+ To reproduce an icon, render its recipe. A recipe fixes its own settings, so do
55
+ not pass `--size`, `--palette`, `--background` or `--language` with it. Replay
56
+ fails if the recipe was edited after it was written or if the drawing no longer
57
+ matches the recorded digests; report that instead of re-rendering from scratch.
58
+
59
+ The same renders are available as the `compose_icon` and `render_icon` MCP
60
+ tools with root-relative paths, and as the `slopcamera.icon.compose` and
61
+ `slopcamera.icon.render` operations.
62
+
63
+ ## Checks
64
+
65
+ - The output is a path-only SVG. Scripts, styles, images, event handlers,
66
+ external references and foreign content are rejected before anything is
67
+ written.
68
+ - Source, output and recipe must be three different files. Existing outputs are
69
+ replaced atomically.
70
+ - Receipts record the source digest, the solved and SVG digests, and the
71
+ library version. Keep the receipt when provenance matters.
72
+ - Inspect the SVG at its intended size. A clean render is not evidence that
73
+ the icon communicates the intended idea.
74
+
75
+ For diagram icons from a third-party icon package, keep using a local adapter as
76
+ described in [customization](customization.md).
@@ -14,14 +14,14 @@ command -v slopcamera
14
14
 
15
15
  A restricted shell can omit package-manager paths. Check known host installation paths before declaring a tool unavailable. If Bun is genuinely absent, follow its official [installation guide](https://bun.sh/docs/installation) within the user’s authorized setup scope. Do not switch package managers or pipe an unreviewed installer into a shell.
16
16
 
17
- Slopcamera installs from its verified release archive: `bun add --global https://github.com/hraness/slopcamera/releases/download/v3.6.0/hraness-slopcamera-3.6.0.tgz`, then `slopcamera doctor --json`. Building from source is the contributor path. Historical Atet archives do not install the renamed CLI; never substitute `slopcamera` into an old archive URL.
17
+ Slopcamera installs from its verified release archive: `bun add --global https://github.com/hraness/slopcamera/releases/download/v3.8.1/hraness-slopcamera-3.8.1.tgz`, then `slopcamera doctor --json`. Building from source is the contributor path. Historical Atet archives do not install the renamed CLI; never substitute `slopcamera` into an old archive URL.
18
18
 
19
19
  If an update from a previous local archive reports `DependencyLoop`, use Bun's
20
20
  [named tarball syntax](https://bun.com/docs/pm/cli/add#tarball-dependencies) with
21
21
  the same verified archive:
22
22
 
23
23
  ```sh
24
- bun add --global @hraness/slopcamera@https://github.com/hraness/slopcamera/releases/download/v3.6.0/hraness-slopcamera-3.6.0.tgz
24
+ bun add --global @hraness/slopcamera@https://github.com/hraness/slopcamera/releases/download/v3.8.1/hraness-slopcamera-3.8.1.tgz
25
25
  slopcamera --version
26
26
  ```
27
27
 
@@ -18,8 +18,16 @@ delivery. Keep the scene's deliberate style and original media intact.
18
18
 
19
19
  Use the user's selected track and supplied tempo. Establish an explicit duration,
20
20
  beat-zero offset, and beats per bar from the task or relevant audio inspection.
21
- The first version does not detect tempo, downbeats, or track duration. Do not
22
- replace explicit musical timing with an unsupported automatic-analysis claim.
21
+ The first version does not detect tempo, downbeats, or track duration from
22
+ audio. Do not replace explicit musical timing with an unsupported
23
+ automatic-analysis claim.
24
+
25
+ When the track was made from a Soundfish score or a MIDI file, derive the timing
26
+ from that source instead with
27
+ `slopcamera media soundtrack grid <score> --start-us <n> --json`. Its `music`
28
+ object drops straight into `parameters.music` below, and its section cue times
29
+ mark shot changes. See [soundtracks](soundtracks.md). Take `timing.durationUs`
30
+ from the audio file itself.
23
31
 
24
32
  Check the installed command and host before preparing exact source:
25
33
 
@@ -0,0 +1,58 @@
1
+ # Derive a soundtrack beat grid
2
+
3
+ Use this workflow when a music video or timed HTML scene has a Soundfish score
4
+ or a Standard MIDI file, and the scene needs the track's tempo, meter and
5
+ section cue times. Slopcamera bundles the Soundfish library at one exact version
6
+ to read scores. It renders no audio: pair the grid with the audio file the user
7
+ mixes or supplies.
8
+
9
+ ## Supported sources
10
+
11
+ One local file of at most 1 MiB, detected from its content or set with
12
+ `--format`:
13
+
14
+ - `compose`: Soundfish compose text for one loop.
15
+ - `song`: Soundfish song text with named sections.
16
+ - `json`: a Soundfish loop or song document.
17
+ - `midi`: a Standard MIDI file with one constant tempo and meter.
18
+
19
+ To write or edit scores, install the `@hraness/soundfish` package and use its
20
+ own agent skill. Slopcamera does not bundle that skill.
21
+
22
+ ## Verify a score
23
+
24
+ ```sh
25
+ slopcamera media soundtrack compose music/hook.song --json
26
+ slopcamera media soundtrack compose music/hook.song --output music/hook.json
27
+ ```
28
+
29
+ `compose` parses and verifies the score and reports its canonical digest, kind
30
+ (loop or song), tempo, beats per bar, bars and duration. `--output` writes the
31
+ verified score document as `.json`. Report any warnings the receipt carries.
32
+
33
+ ## Derive the grid
34
+
35
+ ```sh
36
+ slopcamera media soundtrack grid music/hook.song --start-us 500000 \
37
+ --output music/hook.grid.json --json
38
+ ```
39
+
40
+ The receipt's `music` object is exactly the `{ "bpm", "beatOffsetUs",
41
+ "beatsPerBar" }` shape an HTML scene request accepts under
42
+ `parameters.music`. `--start-us` is the timeline microsecond where the first
43
+ beat lands; it defaults to `0`. Each section carries its label, repeat, bars,
44
+ beats and `startUs`/`endUs` cue times on the same timeline.
45
+
46
+ - Cue times are whole microseconds and never drift more than half a microsecond
47
+ from the beat they name.
48
+ - Sections tile the score from `beatOffsetUs` to `endUs` with no gaps.
49
+ - The same score always yields the same digest and grid.
50
+
51
+ Use the grid in [music videos](music-video.md): copy `music` into the scene
52
+ request and use the section cues for shot changes. Set `timing.durationUs` from
53
+ the audio file, not only from the grid, because a mixed track can have a
54
+ different tail.
55
+
56
+ The same reads are available as the `compose_soundtrack` and
57
+ `derive_soundtrack_grid` MCP tools with root-relative paths, and as the
58
+ `slopcamera.soundtrack.compose` and `slopcamera.soundtrack.grid` operations.
@@ -0,0 +1,135 @@
1
+ import { createHash, randomUUID } from "node:crypto"
2
+ import { constants } from "node:fs"
3
+ import { mkdir, open, rename, rm, stat } from "node:fs/promises"
4
+ import { dirname, join, resolve } from "node:path"
5
+
6
+ /**
7
+ * Bounded local file reads and atomic replaceable writes shared by the
8
+ * icon-scene and soundtrack operations. Reads check the size before and
9
+ * after reading so a growing file cannot exceed the declared bound.
10
+ */
11
+
12
+ export class BoundedFileError extends Error {
13
+ readonly code: "SOURCE_NOT_FOUND" | "SOURCE_NOT_FILE" | "SOURCE_TOO_LARGE" | "SOURCE_UNREADABLE"
14
+
15
+ constructor(code: BoundedFileError["code"], message: string) {
16
+ super(message)
17
+ this.name = "BoundedFileError"
18
+ this.code = code
19
+ }
20
+ }
21
+
22
+ function errorCode(error: unknown): string | undefined {
23
+ return typeof error === "object" && error !== null && "code" in error
24
+ ? String((error as { readonly code: unknown }).code)
25
+ : undefined
26
+ }
27
+
28
+ export async function readBoundedFile(
29
+ path: string,
30
+ maximumBytes: number,
31
+ label: string,
32
+ ): Promise<Uint8Array> {
33
+ let handle
34
+ try {
35
+ // Non-blocking so a FIFO or device fails the regular-file check instead of hanging.
36
+ handle = await open(path, constants.O_RDONLY | constants.O_NONBLOCK)
37
+ } catch (error) {
38
+ if (errorCode(error) === "ENOENT") {
39
+ throw new BoundedFileError("SOURCE_NOT_FOUND", `${label} does not exist.`)
40
+ }
41
+ throw new BoundedFileError("SOURCE_UNREADABLE", `${label} could not be opened.`)
42
+ }
43
+ try {
44
+ const metadata = await handle.stat()
45
+ if (!metadata.isFile()) {
46
+ throw new BoundedFileError("SOURCE_NOT_FILE", `${label} must be a regular file.`)
47
+ }
48
+ if (metadata.size > maximumBytes) {
49
+ throw new BoundedFileError(
50
+ "SOURCE_TOO_LARGE",
51
+ `${label} exceeds the ${String(maximumBytes)}-byte limit.`,
52
+ )
53
+ }
54
+ // Read one byte past the bound so a file that grew after stat fails closed.
55
+ const buffer = new Uint8Array(maximumBytes + 1)
56
+ let length = 0
57
+ for (;;) {
58
+ const { bytesRead } = await handle.read(buffer, length, buffer.length - length, length)
59
+ if (bytesRead === 0) break
60
+ length += bytesRead
61
+ if (length > maximumBytes) {
62
+ throw new BoundedFileError(
63
+ "SOURCE_TOO_LARGE",
64
+ `${label} exceeds the ${String(maximumBytes)}-byte limit.`,
65
+ )
66
+ }
67
+ }
68
+ return buffer.slice(0, length)
69
+ } finally {
70
+ await handle.close()
71
+ }
72
+ }
73
+
74
+ export function decodeUtf8Source(bytes: Uint8Array, label: string): string {
75
+ try {
76
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(bytes)
77
+ } catch {
78
+ throw new BoundedFileError("SOURCE_UNREADABLE", `${label} must be UTF-8 text.`)
79
+ }
80
+ }
81
+
82
+ export function sha256Hex(value: string | Uint8Array): string {
83
+ return createHash("sha256").update(value).digest("hex")
84
+ }
85
+
86
+ /** Replace `path` atomically with `value`, creating its parent directory. */
87
+ export async function publishReplaceableFile(
88
+ path: string,
89
+ value: string | Uint8Array,
90
+ ): Promise<void> {
91
+ const directory = dirname(path)
92
+ await mkdir(directory, { recursive: true })
93
+ // A fixed-length name stays under NAME_MAX whatever the target's basename.
94
+ const temporaryPath = join(directory, `.slopcamera-${randomUUID()}.tmp`)
95
+ try {
96
+ const handle = await open(temporaryPath, "wx")
97
+ try {
98
+ await handle.writeFile(value)
99
+ await handle.sync()
100
+ } finally {
101
+ await handle.close()
102
+ }
103
+ await rename(temporaryPath, path)
104
+ } finally {
105
+ await rm(temporaryPath, { force: true }).catch(() => undefined)
106
+ }
107
+ }
108
+
109
+ /**
110
+ * True when two declared paths name the same file, such as an output that
111
+ * would replace its source. Equal resolved spellings collide; existing files
112
+ * also collide by device and inode, which catches links and case-insensitive
113
+ * spellings of one file.
114
+ */
115
+ export async function hasPathCollision(paths: readonly (string | undefined)[]): Promise<boolean> {
116
+ const spellings = new Set<string>()
117
+ const identities = new Set<string>()
118
+ for (const path of paths) {
119
+ if (path === undefined) continue
120
+ const absolute = resolve(path)
121
+ if (spellings.has(absolute)) return true
122
+ spellings.add(absolute)
123
+ let identity: string
124
+ try {
125
+ const metadata = await stat(absolute, { bigint: true })
126
+ identity = `${String(metadata.dev)}:${String(metadata.ino)}`
127
+ } catch (error) {
128
+ if (errorCode(error) === "ENOENT" || errorCode(error) === "ENOTDIR") continue
129
+ throw new BoundedFileError("SOURCE_UNREADABLE", "A declared path could not be inspected.")
130
+ }
131
+ if (identities.has(identity)) return true
132
+ identities.add(identity)
133
+ }
134
+ return false
135
+ }