From 28577ac744e342c282263ba746ace8a1c46b3049 Mon Sep 17 00:00:00 2001 From: TheDesignFounder Date: Thu, 24 Sep 2026 23:47:28 -0700 Subject: [PATCH 1/2] Generate the Copilot and Cline skill paths from one source GitHub Copilot reads .github/skills and Cline reads .cline/skills, so both now carry the nine workflows. Neither is a fork: scripts/sync_skills.py copies them byte for byte from plugins/dreamlayer/skills, the canonical source, alongside the existing .agents/skills copy that Cursor, OpenCode and the npx skills installer read. Copies are real files rather than symlinks so Windows checkouts and archive downloads keep working. --check reports any drift and runs in CI and in validate.py, and tests cover an edited copy, a stray file, a changed vendored file and the case where a mirror tries to write back to the source. scripts/build_plugins.py generated the plugin copies from .agents/skills and is replaced by the new script. docs/copilot-setup.md and docs/cline-setup.md cover installation, key handling, the seven tools, the generate then poll or resume then download flow, credit limits and recovery, and they repeat that Unity editor import is still unconfirmed. Verified in a clean profile with no account and no generation: Copilot CLI 1.0.88 listed all nine from .github/skills and registered the pinned MCP server with the key masked; Cline CLI 3.0.65 listed all nine from .cline/skills and listed the same server from its settings file. Co-authored-by: Claude Opus 5 (1M context) --- .agents/skills/README.md | 14 ++ .cline/skills/README.md | 14 ++ .cline/skills/batch-photo-editing/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .cline/skills/generate-or-edit-image/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .cline/skills/godot-sprite-import/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../scripts/import_sprite.py | 168 ++++++++++++++++++ .../logo-and-app-icon-concepts/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../marketing-image-variations/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../skills/print-on-demand-artwork/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../skills/product-image-generation/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../sprite-sheet-from-reference/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .cline/skills/unity-sprite-import/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../scripts/import_sprite.py | 168 ++++++++++++++++++ .github/skills/README.md | 14 ++ .github/skills/batch-photo-editing/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../skills/generate-or-edit-image/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .github/skills/godot-sprite-import/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../scripts/import_sprite.py | 168 ++++++++++++++++++ .../logo-and-app-icon-concepts/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../marketing-image-variations/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../skills/print-on-demand-artwork/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../skills/product-image-generation/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../sprite-sheet-from-reference/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .github/skills/unity-sprite-import/SKILL.md | 11 ++ .../references/workflow-contract.md | 46 +++++ .../scripts/import_sprite.py | 168 ++++++++++++++++++ .github/workflows/validate.yml | 1 + README.md | 15 +- docs/client-setup.md | 18 +- docs/cline-setup.md | 96 ++++++++++ docs/copilot-setup.md | 86 +++++++++ scripts/build_plugins.py | 13 -- scripts/sync_skills.py | 113 ++++++++++++ scripts/validate.py | 30 ++-- tests/test_sync_skills.py | 86 +++++++++ 52 files changed, 2167 insertions(+), 31 deletions(-) create mode 100644 .agents/skills/README.md create mode 100644 .cline/skills/README.md create mode 100644 .cline/skills/batch-photo-editing/SKILL.md create mode 100644 .cline/skills/batch-photo-editing/references/workflow-contract.md create mode 100644 .cline/skills/generate-or-edit-image/SKILL.md create mode 100644 .cline/skills/generate-or-edit-image/references/workflow-contract.md create mode 100644 .cline/skills/godot-sprite-import/SKILL.md create mode 100644 .cline/skills/godot-sprite-import/references/workflow-contract.md create mode 100644 .cline/skills/godot-sprite-import/scripts/import_sprite.py create mode 100644 .cline/skills/logo-and-app-icon-concepts/SKILL.md create mode 100644 .cline/skills/logo-and-app-icon-concepts/references/workflow-contract.md create mode 100644 .cline/skills/marketing-image-variations/SKILL.md create mode 100644 .cline/skills/marketing-image-variations/references/workflow-contract.md create mode 100644 .cline/skills/print-on-demand-artwork/SKILL.md create mode 100644 .cline/skills/print-on-demand-artwork/references/workflow-contract.md create mode 100644 .cline/skills/product-image-generation/SKILL.md create mode 100644 .cline/skills/product-image-generation/references/workflow-contract.md create mode 100644 .cline/skills/sprite-sheet-from-reference/SKILL.md create mode 100644 .cline/skills/sprite-sheet-from-reference/references/workflow-contract.md create mode 100644 .cline/skills/unity-sprite-import/SKILL.md create mode 100644 .cline/skills/unity-sprite-import/references/workflow-contract.md create mode 100644 .cline/skills/unity-sprite-import/scripts/import_sprite.py create mode 100644 .github/skills/README.md create mode 100644 .github/skills/batch-photo-editing/SKILL.md create mode 100644 .github/skills/batch-photo-editing/references/workflow-contract.md create mode 100644 .github/skills/generate-or-edit-image/SKILL.md create mode 100644 .github/skills/generate-or-edit-image/references/workflow-contract.md create mode 100644 .github/skills/godot-sprite-import/SKILL.md create mode 100644 .github/skills/godot-sprite-import/references/workflow-contract.md create mode 100644 .github/skills/godot-sprite-import/scripts/import_sprite.py create mode 100644 .github/skills/logo-and-app-icon-concepts/SKILL.md create mode 100644 .github/skills/logo-and-app-icon-concepts/references/workflow-contract.md create mode 100644 .github/skills/marketing-image-variations/SKILL.md create mode 100644 .github/skills/marketing-image-variations/references/workflow-contract.md create mode 100644 .github/skills/print-on-demand-artwork/SKILL.md create mode 100644 .github/skills/print-on-demand-artwork/references/workflow-contract.md create mode 100644 .github/skills/product-image-generation/SKILL.md create mode 100644 .github/skills/product-image-generation/references/workflow-contract.md create mode 100644 .github/skills/sprite-sheet-from-reference/SKILL.md create mode 100644 .github/skills/sprite-sheet-from-reference/references/workflow-contract.md create mode 100644 .github/skills/unity-sprite-import/SKILL.md create mode 100644 .github/skills/unity-sprite-import/references/workflow-contract.md create mode 100644 .github/skills/unity-sprite-import/scripts/import_sprite.py create mode 100644 docs/cline-setup.md create mode 100644 docs/copilot-setup.md delete mode 100644 scripts/build_plugins.py create mode 100644 scripts/sync_skills.py create mode 100644 tests/test_sync_skills.py diff --git a/.agents/skills/README.md b/.agents/skills/README.md new file mode 100644 index 0000000..92ae556 --- /dev/null +++ b/.agents/skills/README.md @@ -0,0 +1,14 @@ +# Generated skills + +Do not edit these files. They are copied byte for byte from +`plugins/dreamlayer/skills/`, the canonical source, so that Cursor, OpenCode, GitHub Copilot and the npx skills installer load the same +workflows as every other host. + +Change a workflow in the canonical source, then regenerate: + +```sh +python3 scripts/sync_skills.py +``` + +`python3 scripts/sync_skills.py --check` fails when any copy has drifted, and +`python3 scripts/validate.py` runs that check. diff --git a/.cline/skills/README.md b/.cline/skills/README.md new file mode 100644 index 0000000..b465386 --- /dev/null +++ b/.cline/skills/README.md @@ -0,0 +1,14 @@ +# Generated skills + +Do not edit these files. They are copied byte for byte from +`plugins/dreamlayer/skills/`, the canonical source, so that Cline load the same +workflows as every other host. + +Change a workflow in the canonical source, then regenerate: + +```sh +python3 scripts/sync_skills.py +``` + +`python3 scripts/sync_skills.py --check` fails when any copy has drifted, and +`python3 scripts/validate.py` runs that check. diff --git a/.cline/skills/batch-photo-editing/SKILL.md b/.cline/skills/batch-photo-editing/SKILL.md new file mode 100644 index 0000000..fa8ab45 --- /dev/null +++ b/.cline/skills/batch-photo-editing/SKILL.md @@ -0,0 +1,11 @@ +--- +name: batch-photo-editing +description: "Apply a specified edit, background removal or upscaling task to a list of local images with DreamLayer. Use for a bounded batch with per-file results and a total credit budget." +--- + +# batch photo editing + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Enumerate input files and assign unique new outputs before uploading. Estimate total operations from current capabilities; an edit plus cutout is two paid operations per image. Maintain a per-item ledger of input, asset ID, saved request, idempotency key, execution ID, status and output. Process sequentially initially; stop new submissions on insufficient credits or exhausted budget. Resume existing executions after interruption. Skip already completed outputs rather than changing paths and charging again. Inspect a sample first and all outputs for tasks where fidelity matters. The batch is orchestration over individual operations, not a separate DreamLayer batch endpoint. + diff --git a/.cline/skills/batch-photo-editing/references/workflow-contract.md b/.cline/skills/batch-photo-editing/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.cline/skills/batch-photo-editing/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.cline/skills/generate-or-edit-image/SKILL.md b/.cline/skills/generate-or-edit-image/SKILL.md new file mode 100644 index 0000000..82e3df5 --- /dev/null +++ b/.cline/skills/generate-or-edit-image/SKILL.md @@ -0,0 +1,11 @@ +--- +name: generate-or-edit-image +description: "Generate an original image, edit a local reference, remove a background, or upscale an image with DreamLayer. Use for visual assets in an app, website or creative project when the user wants image files." +--- + +# generate or edit image + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Establish the visual brief, output count, aspect ratio, destination and authorized credits. Select the operation by the requested change; an edit needs a reference, a new image does not. Use background_remove for transparency rather than assuming a prompt creates alpha. Upscale doubles both dimensions; input longest side must be <=2048 and output is capped at 4096. Supported aspect ratios: 1:1, 16:9, 9:16, 4:3, 3:4. Save files into the project, inspect, and report dimensions and remaining limitations. For logos, product photos, marketing and print artwork, read the matching installed task skill if present. + diff --git a/.cline/skills/generate-or-edit-image/references/workflow-contract.md b/.cline/skills/generate-or-edit-image/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.cline/skills/generate-or-edit-image/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.cline/skills/godot-sprite-import/SKILL.md b/.cline/skills/godot-sprite-import/SKILL.md new file mode 100644 index 0000000..e35e069 --- /dev/null +++ b/.cline/skills/godot-sprite-import/SKILL.md @@ -0,0 +1,11 @@ +--- +name: godot-sprite-import +description: "Import an existing DreamLayer sprite ZIP into a Godot 4 demo and play the animation. Use when the user already has generated frames and wants a runnable game-engine example." +--- + +# godot sprite import + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +This is local import and spends no credits. Use scripts/import_sprite.py with --engine godot --zip /absolute/walk.zip --out /absolute/new-demo. It reads PNG frames and atlas durations from the bundle, validates them and creates a Godot 4 project with AnimatedSprite2D. Run godot --headless --path /absolute/new-demo --editor --quit, then open the project and inspect playback. Preserve returned frame durations; do not assume constant FPS. Use the generated demo as a reference when importing into an existing project, keeping existing settings intact. If Godot is unavailable, mark engine validation pending; successful file generation alone does not prove engine playback. + diff --git a/.cline/skills/godot-sprite-import/references/workflow-contract.md b/.cline/skills/godot-sprite-import/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.cline/skills/godot-sprite-import/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.cline/skills/godot-sprite-import/scripts/import_sprite.py b/.cline/skills/godot-sprite-import/scripts/import_sprite.py new file mode 100644 index 0000000..217d493 --- /dev/null +++ b/.cline/skills/godot-sprite-import/scripts/import_sprite.py @@ -0,0 +1,168 @@ +"""Import a DreamLayer ZIP into a new Godot/Unity example. No API calls or dependencies.""" +import argparse, json, math, re, struct, zipfile +from pathlib import Path, PurePosixPath + +GODOT_SCRIPT = '''extends Node2D +var actor: AnimatedSprite2D +func _ready() -> void: + var data = JSON.parse_string(FileAccess.get_file_as_string("res://frames/manifest.json")) + var animation := SpriteFrames.new() + animation.add_animation("dreamlayer") + animation.set_animation_speed("dreamlayer", 1.0) + animation.set_animation_loop("dreamlayer", data.loop) + for i in range(data.frames.size()): + var texture = load("res://frames/" + data.frames[i] + ".png") + assert(texture != null, "Missing sprite texture") + animation.add_frame("dreamlayer", texture, data.durations_ms[i] / 1000.0) + actor = AnimatedSprite2D.new() + actor.sprite_frames = animation + actor.position = Vector2(320, 220) + actor.scale = Vector2(2, 2) + add_child(actor) + actor.play("dreamlayer") + if "--smoke" in OS.get_cmdline_user_args(): + var before := actor.frame + await get_tree().create_timer(0.9).timeout + assert(actor.is_playing(), "Animation stopped") + assert(actor.frame != before, "Animation did not advance") + print("DREAMLAYER_IMPORT_OK frames=", animation.get_frame_count("dreamlayer")) + get_tree().quit() +''' + +UNITY_PLAYER = '''using System; +using UnityEngine; +[RequireComponent(typeof(SpriteRenderer))] +public class DreamLayerFramePlayer : MonoBehaviour { + [Serializable] public class Manifest { + public string[] frames; + public float[] durations_ms; + public bool loop; + } + private Sprite[] frames; + private Manifest manifest; + private SpriteRenderer target; + private int index; + private float elapsed; + private bool finished; + void Start() { + var text = Resources.Load("DreamLayerDemo/manifest"); + if (text == null) { Debug.LogError("Missing DreamLayer manifest"); enabled=false; return; } + manifest = JsonUtility.FromJson(text.text); + if (manifest.frames == null || manifest.frames.Length == 0 || manifest.durations_ms == null || manifest.frames.Length != manifest.durations_ms.Length) { + Debug.LogError("Invalid DreamLayer timing manifest"); enabled=false; return; + } + target = GetComponent(); + frames = new Sprite[manifest.frames.Length]; + for(int i=0;i("DreamLayerDemo/"+manifest.frames[i]); + if(frames[i]==null || manifest.durations_ms[i]<=0) { Debug.LogError("Invalid DreamLayer frame"); enabled=false; return; } + } + target.sprite=frames[0]; + } + void Update() { + if(finished || frames==null) return; + elapsed+=Time.deltaTime; + while(elapsed>=manifest.durations_ms[index]/1000f) { + elapsed-=manifest.durations_ms[index]/1000f; + if(index==frames.Length-1 && !manifest.loop) { finished=true; return; } + index=(index+1)%frames.Length; + target.sprite=frames[index]; + } + } +} +''' + +UNITY_EDITOR = '''using UnityEditor; +using UnityEngine; +public class DreamLayerSpriteImporter : AssetPostprocessor { + void OnPreprocessTexture() { + if(!assetPath.StartsWith("Assets/DreamLayerDemo/Resources/DreamLayerDemo/")) return; + var importer=(TextureImporter)assetImporter; + importer.textureType=TextureImporterType.Sprite; + importer.spriteImportMode=SpriteImportMode.Single; + importer.spritePixelsPerUnit=128; + importer.alphaIsTransparency=true; + importer.mipmapEnabled=false; + importer.textureCompression=TextureImporterCompression.Uncompressed; + } + [MenuItem("Tools/DreamLayer/Create sprite demo")] + public static void CreateDemo() { + var actor=new GameObject("DreamLayer sprite demo"); + Undo.RegisterCreatedObjectUndo(actor,"Create DreamLayer demo"); + actor.AddComponent(); + actor.AddComponent(); + if(Camera.main==null) { + var cameraObject=new GameObject("DreamLayer demo camera"); + Undo.RegisterCreatedObjectUndo(cameraObject,"Create DreamLayer camera"); + cameraObject.tag="MainCamera"; + var camera=cameraObject.AddComponent(); + camera.orthographic=true; camera.orthographicSize=1.5f; + cameraObject.transform.position=new Vector3(0,0,-10); + } + Selection.activeGameObject=actor; + } +} +''' + +def read_bundle(path): + with zipfile.ZipFile(path) as z: + infos=z.infolist() + if len(infos)>150 or sum(i.file_size for i in infos)>200*1024*1024: + raise ValueError('Bundle exceeds import limits') + names=[i.filename for i in infos] + if len(set(names))!=len(names): raise ValueError('Duplicate ZIP entries') + for name in names: + p=PurePosixPath(name) + if p.is_absolute() or '..' in p.parts or '\\' in name: + raise ValueError('Unsafe ZIP path') + atlas=json.loads(z.read('atlas.json')) + if atlas.get('schema_version')!=1: raise ValueError('Unsupported atlas schema') + order=list(atlas['frames']) + if not 7<=len(order)<=100 or atlas['frame_count']!=len(order): + raise ValueError('Frame count mismatch') + durations=atlas.get('frame_durations_ms') + if not isinstance(durations,list) or len(durations)!=len(order): + raise ValueError('Missing per-frame durations') + if any(type(t) not in (int,float) or not math.isfinite(t) or t<=0 for t in durations): + raise ValueError('Invalid duration') + frames={} + for name in order: + if not re.fullmatch(r'[A-Za-z0-9_-]+',name): raise ValueError('Unsafe frame name') + data=z.read('frames/'+name+'.png') + if len(data)<33 or data[:8]!=b'\x89PNG\r\n\x1a\n': raise ValueError('Invalid PNG') + width,height=struct.unpack('>II',data[16:24]) + if width!=atlas['cell'] or height!=atlas['cell']: raise ValueError('Canvas mismatch') + if data[25] not in (4,6): raise ValueError('PNG lacks an alpha channel') + frames[name]=data + manifest={'frames':order,'durations_ms':durations, + 'loop':atlas.get('animation_mode','loop' if atlas.get('action') in ('walk','run','idle') else 'once')=='loop', + 'width':atlas['cell'],'height':atlas['cell']} + return frames,manifest + +def build(bundle,out,engine): + frames,manifest=read_bundle(bundle) + out=Path(out) + if out.exists(): raise ValueError('Destination must be new; refusing overwrite') + out.mkdir(parents=True) + assets=out/'frames' if engine=='godot' else out/'Assets/DreamLayerDemo/Resources/DreamLayerDemo' + assets.mkdir(parents=True) + for name,data in frames.items(): (assets/(name+'.png')).write_bytes(data) + (assets/'manifest.json').write_text(json.dumps(manifest,indent=2)+'\n') + if engine=='godot': + (out/'project.godot').write_text('config_version=5\n[application]\nconfig/name="DreamLayer sprite demo"\nrun/main_scene="res://main.tscn"\n[display]\nwindow/size/viewport_width=640\nwindow/size/viewport_height=440\n[rendering]\nrenderer/rendering_method="gl_compatibility"\n') + (out/'main.tscn').write_text('[gd_scene load_steps=2 format=3]\n[ext_resource type="Script" path="res://main.gd" id="1"]\n[node name="DreamLayerDemo" type="Node2D"]\nscript = ExtResource("1")\n') + (out/'main.gd').write_text(GODOT_SCRIPT) + (out/'.gitignore').write_text('.godot/\n') + else: + base=out/'Assets/DreamLayerDemo' + (base/'Editor').mkdir() + (base/'DreamLayerFramePlayer.cs').write_text(UNITY_PLAYER) + (base/'Editor/DreamLayerSpriteImporter.cs').write_text(UNITY_EDITOR) + print(json.dumps({'engine':engine,'frames':len(frames),'duration_ms':sum(manifest['durations_ms']),'output':str(out)})) + +if __name__=='__main__': + p=argparse.ArgumentParser(description=__doc__) + p.add_argument('--engine',choices=['godot','unity'],required=True) + p.add_argument('--zip',type=Path,required=True) + p.add_argument('--out',type=Path,required=True) + a=p.parse_args();build(a.zip,a.out,a.engine) diff --git a/.cline/skills/logo-and-app-icon-concepts/SKILL.md b/.cline/skills/logo-and-app-icon-concepts/SKILL.md new file mode 100644 index 0000000..4c577b8 --- /dev/null +++ b/.cline/skills/logo-and-app-icon-concepts/SKILL.md @@ -0,0 +1,11 @@ +--- +name: logo-and-app-icon-concepts +description: "Create logo concepts, brand marks or app-icon artwork as raster images with DreamLayer. Use when building a brand or app that needs original visual directions and PNG assets." +--- + +# logo and app icon concepts + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Collect brand name, audience, tone, silhouette and palette; use a small explicitly authorized number of concepts. Request original marks without copying an existing brand. Prefer symbol exploration first; add precise lettering separately if generation misspells it. Generate a square icon concept and inspect it at small size. Remove background in a separate authorized operation if required. Export raster images and explain whether alpha is present. Do not describe a PNG as SVG, a scalable vector master, a cleared trademark or exclusive ownership. A supplied mark should be edited only as requested, preserving its identity. + diff --git a/.cline/skills/logo-and-app-icon-concepts/references/workflow-contract.md b/.cline/skills/logo-and-app-icon-concepts/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.cline/skills/logo-and-app-icon-concepts/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.cline/skills/marketing-image-variations/SKILL.md b/.cline/skills/marketing-image-variations/SKILL.md new file mode 100644 index 0000000..bcfe173 --- /dev/null +++ b/.cline/skills/marketing-image-variations/SKILL.md @@ -0,0 +1,11 @@ +--- +name: marketing-image-variations +description: "Generate ad, banner and social campaign image variations from a brief or reference with DreamLayer. Use when the user needs creative files for a campaign or content calendar." +--- + +# marketing image variations + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Define audience, offer, destination placement, brand colors, required copy, aspect ratio and authorized variant count. Map output ratios to the supported ratios; use an explicit crop/resize step when a placement needs exact pixels. Generate or edit one approved reference direction and inspect each variation. Check text and product claims. For precise typography, use generated imagery as a background and add text in the project's design/code tools. Save an output manifest with brief, filename, dimensions and review status. No ad account upload, spend or campaign launch is implied. + diff --git a/.cline/skills/marketing-image-variations/references/workflow-contract.md b/.cline/skills/marketing-image-variations/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.cline/skills/marketing-image-variations/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.cline/skills/print-on-demand-artwork/SKILL.md b/.cline/skills/print-on-demand-artwork/SKILL.md new file mode 100644 index 0000000..ffcee32 --- /dev/null +++ b/.cline/skills/print-on-demand-artwork/SKILL.md @@ -0,0 +1,11 @@ +--- +name: print-on-demand-artwork +description: "Create raster artwork concepts for shirts, stickers, posters and print-on-demand products using DreamLayer. Use when a seller needs original artwork and transparent PNG preparation." +--- + +# print on demand artwork + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Ask for intended physical size and the print provider's pixel requirements when the user needs final production files. Generate artwork concepts, with background_remove as a separate operation if transparency is needed. Inspect actual pixel dimensions and alpha; calculate effective PPI as pixels divided by inches. Do not label the output print-ready until it meets the specific supplier's requirements. Upscaling does not guarantee original detail. Do not promise vectors, seamless repeat patterns, embroidery files, color-profile conversion or store publishing. If requirements exceed supported output, provide the concept and state the remaining production step. + diff --git a/.cline/skills/print-on-demand-artwork/references/workflow-contract.md b/.cline/skills/print-on-demand-artwork/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.cline/skills/print-on-demand-artwork/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.cline/skills/product-image-generation/SKILL.md b/.cline/skills/product-image-generation/SKILL.md new file mode 100644 index 0000000..88ce976 --- /dev/null +++ b/.cline/skills/product-image-generation/SKILL.md @@ -0,0 +1,11 @@ +--- +name: product-image-generation +description: "Create ecommerce product-photo backgrounds and lifestyle image concepts from a supplied product photo using DreamLayer. Use when preparing store images or product campaign visuals." +--- + +# product image generation + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Require a reference photo for a real product. Identify the product details that must remain: shape, label, logo, color, count and proportions. Use image_to_image with a specific setting and composition; use background_remove for a clean cutout. Inspect the result against the reference and flag altered details before it is used in a listing. DreamLayer generation is not a guarantee of SKU fidelity. Export local files; do not publish listings or claim storefront integration. Generate one reviewable candidate before expanding an authorized batch. + diff --git a/.cline/skills/product-image-generation/references/workflow-contract.md b/.cline/skills/product-image-generation/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.cline/skills/product-image-generation/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.cline/skills/sprite-sheet-from-reference/SKILL.md b/.cline/skills/sprite-sheet-from-reference/SKILL.md new file mode 100644 index 0000000..1f946dc --- /dev/null +++ b/.cline/skills/sprite-sheet-from-reference/SKILL.md @@ -0,0 +1,11 @@ +--- +name: sprite-sheet-from-reference +description: "Turn one character, object or effect reference into an animated sprite-sheet ZIP with transparent PNG frames using DreamLayer. Use for game sprites, walk cycles, idle animations or custom motion within a credit budget." +--- + +# sprite sheet from reference + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Read the workflow contract, then current sprite_pricing in capabilities. Exactly one reference and one of action (walk/run/idle) or animation_prompt (1–4000 chars) are required. Choose frame_count 7–100 and square frame_size 32,64,128,256,512,720,1080. action defaults to loop; custom animation defaults to once; explicitly set animation_mode when needed. Quote in integer cents: min(n,14)*14 + max(n-14,0)*7, divide by 17 cents per credit, round the WHOLE order upward once to 0.1 credit. Treat these as the current example values, and recompute from live capabilities. 7 frames currently cost 5.8 credits; 12 cost 9.9. Set max_credits within the user's authorized cap. Compare to available balance, not the separately rounded funding buckets. Sprites have no customer cancellation. Save execution and poll/resume; failed/expired work restores the hold. Download .zip, inspect its manifest and preview, and validate frame count/alpha/timing. Do not claim pixel-grid preservation, perfect anatomy, eight directions, editable Aseprite layers or production quality. For engine import use the matching engine skill. CLI: dreamlayer sprite reference.png --action walk --frames 7 --frame-size 128 --max-credits 5.8 --out walk.zip --json + diff --git a/.cline/skills/sprite-sheet-from-reference/references/workflow-contract.md b/.cline/skills/sprite-sheet-from-reference/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.cline/skills/sprite-sheet-from-reference/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.cline/skills/unity-sprite-import/SKILL.md b/.cline/skills/unity-sprite-import/SKILL.md new file mode 100644 index 0000000..b66536b --- /dev/null +++ b/.cline/skills/unity-sprite-import/SKILL.md @@ -0,0 +1,11 @@ +--- +name: unity-sprite-import +description: "Prepare an existing DreamLayer sprite ZIP for Unity 2D with a local frame player and texture importer. Use when a Unity project needs the downloaded animation imported and played." +--- + +# unity sprite import + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +This is local import and spends no credits. Use scripts/import_sprite.py with --engine unity --zip /absolute/walk.zip --out /absolute/new-demo. It generates an Assets/DreamLayerDemo folder, sprite import settings, a C# frame player with each frame's timing, and an Editor menu to create the demo. Copy Assets into a Unity project, wait for script compilation, then run Tools > DreamLayer > Create sprite demo and press Play. Do not overwrite existing assets without checking the destination. If the Unity editor is unavailable, mark compile/playback validation pending. This is an ordinary Unity import example, not a claim of inclusion in Unity's official plugin. + diff --git a/.cline/skills/unity-sprite-import/references/workflow-contract.md b/.cline/skills/unity-sprite-import/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.cline/skills/unity-sprite-import/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.cline/skills/unity-sprite-import/scripts/import_sprite.py b/.cline/skills/unity-sprite-import/scripts/import_sprite.py new file mode 100644 index 0000000..217d493 --- /dev/null +++ b/.cline/skills/unity-sprite-import/scripts/import_sprite.py @@ -0,0 +1,168 @@ +"""Import a DreamLayer ZIP into a new Godot/Unity example. No API calls or dependencies.""" +import argparse, json, math, re, struct, zipfile +from pathlib import Path, PurePosixPath + +GODOT_SCRIPT = '''extends Node2D +var actor: AnimatedSprite2D +func _ready() -> void: + var data = JSON.parse_string(FileAccess.get_file_as_string("res://frames/manifest.json")) + var animation := SpriteFrames.new() + animation.add_animation("dreamlayer") + animation.set_animation_speed("dreamlayer", 1.0) + animation.set_animation_loop("dreamlayer", data.loop) + for i in range(data.frames.size()): + var texture = load("res://frames/" + data.frames[i] + ".png") + assert(texture != null, "Missing sprite texture") + animation.add_frame("dreamlayer", texture, data.durations_ms[i] / 1000.0) + actor = AnimatedSprite2D.new() + actor.sprite_frames = animation + actor.position = Vector2(320, 220) + actor.scale = Vector2(2, 2) + add_child(actor) + actor.play("dreamlayer") + if "--smoke" in OS.get_cmdline_user_args(): + var before := actor.frame + await get_tree().create_timer(0.9).timeout + assert(actor.is_playing(), "Animation stopped") + assert(actor.frame != before, "Animation did not advance") + print("DREAMLAYER_IMPORT_OK frames=", animation.get_frame_count("dreamlayer")) + get_tree().quit() +''' + +UNITY_PLAYER = '''using System; +using UnityEngine; +[RequireComponent(typeof(SpriteRenderer))] +public class DreamLayerFramePlayer : MonoBehaviour { + [Serializable] public class Manifest { + public string[] frames; + public float[] durations_ms; + public bool loop; + } + private Sprite[] frames; + private Manifest manifest; + private SpriteRenderer target; + private int index; + private float elapsed; + private bool finished; + void Start() { + var text = Resources.Load("DreamLayerDemo/manifest"); + if (text == null) { Debug.LogError("Missing DreamLayer manifest"); enabled=false; return; } + manifest = JsonUtility.FromJson(text.text); + if (manifest.frames == null || manifest.frames.Length == 0 || manifest.durations_ms == null || manifest.frames.Length != manifest.durations_ms.Length) { + Debug.LogError("Invalid DreamLayer timing manifest"); enabled=false; return; + } + target = GetComponent(); + frames = new Sprite[manifest.frames.Length]; + for(int i=0;i("DreamLayerDemo/"+manifest.frames[i]); + if(frames[i]==null || manifest.durations_ms[i]<=0) { Debug.LogError("Invalid DreamLayer frame"); enabled=false; return; } + } + target.sprite=frames[0]; + } + void Update() { + if(finished || frames==null) return; + elapsed+=Time.deltaTime; + while(elapsed>=manifest.durations_ms[index]/1000f) { + elapsed-=manifest.durations_ms[index]/1000f; + if(index==frames.Length-1 && !manifest.loop) { finished=true; return; } + index=(index+1)%frames.Length; + target.sprite=frames[index]; + } + } +} +''' + +UNITY_EDITOR = '''using UnityEditor; +using UnityEngine; +public class DreamLayerSpriteImporter : AssetPostprocessor { + void OnPreprocessTexture() { + if(!assetPath.StartsWith("Assets/DreamLayerDemo/Resources/DreamLayerDemo/")) return; + var importer=(TextureImporter)assetImporter; + importer.textureType=TextureImporterType.Sprite; + importer.spriteImportMode=SpriteImportMode.Single; + importer.spritePixelsPerUnit=128; + importer.alphaIsTransparency=true; + importer.mipmapEnabled=false; + importer.textureCompression=TextureImporterCompression.Uncompressed; + } + [MenuItem("Tools/DreamLayer/Create sprite demo")] + public static void CreateDemo() { + var actor=new GameObject("DreamLayer sprite demo"); + Undo.RegisterCreatedObjectUndo(actor,"Create DreamLayer demo"); + actor.AddComponent(); + actor.AddComponent(); + if(Camera.main==null) { + var cameraObject=new GameObject("DreamLayer demo camera"); + Undo.RegisterCreatedObjectUndo(cameraObject,"Create DreamLayer camera"); + cameraObject.tag="MainCamera"; + var camera=cameraObject.AddComponent(); + camera.orthographic=true; camera.orthographicSize=1.5f; + cameraObject.transform.position=new Vector3(0,0,-10); + } + Selection.activeGameObject=actor; + } +} +''' + +def read_bundle(path): + with zipfile.ZipFile(path) as z: + infos=z.infolist() + if len(infos)>150 or sum(i.file_size for i in infos)>200*1024*1024: + raise ValueError('Bundle exceeds import limits') + names=[i.filename for i in infos] + if len(set(names))!=len(names): raise ValueError('Duplicate ZIP entries') + for name in names: + p=PurePosixPath(name) + if p.is_absolute() or '..' in p.parts or '\\' in name: + raise ValueError('Unsafe ZIP path') + atlas=json.loads(z.read('atlas.json')) + if atlas.get('schema_version')!=1: raise ValueError('Unsupported atlas schema') + order=list(atlas['frames']) + if not 7<=len(order)<=100 or atlas['frame_count']!=len(order): + raise ValueError('Frame count mismatch') + durations=atlas.get('frame_durations_ms') + if not isinstance(durations,list) or len(durations)!=len(order): + raise ValueError('Missing per-frame durations') + if any(type(t) not in (int,float) or not math.isfinite(t) or t<=0 for t in durations): + raise ValueError('Invalid duration') + frames={} + for name in order: + if not re.fullmatch(r'[A-Za-z0-9_-]+',name): raise ValueError('Unsafe frame name') + data=z.read('frames/'+name+'.png') + if len(data)<33 or data[:8]!=b'\x89PNG\r\n\x1a\n': raise ValueError('Invalid PNG') + width,height=struct.unpack('>II',data[16:24]) + if width!=atlas['cell'] or height!=atlas['cell']: raise ValueError('Canvas mismatch') + if data[25] not in (4,6): raise ValueError('PNG lacks an alpha channel') + frames[name]=data + manifest={'frames':order,'durations_ms':durations, + 'loop':atlas.get('animation_mode','loop' if atlas.get('action') in ('walk','run','idle') else 'once')=='loop', + 'width':atlas['cell'],'height':atlas['cell']} + return frames,manifest + +def build(bundle,out,engine): + frames,manifest=read_bundle(bundle) + out=Path(out) + if out.exists(): raise ValueError('Destination must be new; refusing overwrite') + out.mkdir(parents=True) + assets=out/'frames' if engine=='godot' else out/'Assets/DreamLayerDemo/Resources/DreamLayerDemo' + assets.mkdir(parents=True) + for name,data in frames.items(): (assets/(name+'.png')).write_bytes(data) + (assets/'manifest.json').write_text(json.dumps(manifest,indent=2)+'\n') + if engine=='godot': + (out/'project.godot').write_text('config_version=5\n[application]\nconfig/name="DreamLayer sprite demo"\nrun/main_scene="res://main.tscn"\n[display]\nwindow/size/viewport_width=640\nwindow/size/viewport_height=440\n[rendering]\nrenderer/rendering_method="gl_compatibility"\n') + (out/'main.tscn').write_text('[gd_scene load_steps=2 format=3]\n[ext_resource type="Script" path="res://main.gd" id="1"]\n[node name="DreamLayerDemo" type="Node2D"]\nscript = ExtResource("1")\n') + (out/'main.gd').write_text(GODOT_SCRIPT) + (out/'.gitignore').write_text('.godot/\n') + else: + base=out/'Assets/DreamLayerDemo' + (base/'Editor').mkdir() + (base/'DreamLayerFramePlayer.cs').write_text(UNITY_PLAYER) + (base/'Editor/DreamLayerSpriteImporter.cs').write_text(UNITY_EDITOR) + print(json.dumps({'engine':engine,'frames':len(frames),'duration_ms':sum(manifest['durations_ms']),'output':str(out)})) + +if __name__=='__main__': + p=argparse.ArgumentParser(description=__doc__) + p.add_argument('--engine',choices=['godot','unity'],required=True) + p.add_argument('--zip',type=Path,required=True) + p.add_argument('--out',type=Path,required=True) + a=p.parse_args();build(a.zip,a.out,a.engine) diff --git a/.github/skills/README.md b/.github/skills/README.md new file mode 100644 index 0000000..e3d22e5 --- /dev/null +++ b/.github/skills/README.md @@ -0,0 +1,14 @@ +# Generated skills + +Do not edit these files. They are copied byte for byte from +`plugins/dreamlayer/skills/`, the canonical source, so that GitHub Copilot in the CLI, VS Code and the coding agent load the same +workflows as every other host. + +Change a workflow in the canonical source, then regenerate: + +```sh +python3 scripts/sync_skills.py +``` + +`python3 scripts/sync_skills.py --check` fails when any copy has drifted, and +`python3 scripts/validate.py` runs that check. diff --git a/.github/skills/batch-photo-editing/SKILL.md b/.github/skills/batch-photo-editing/SKILL.md new file mode 100644 index 0000000..fa8ab45 --- /dev/null +++ b/.github/skills/batch-photo-editing/SKILL.md @@ -0,0 +1,11 @@ +--- +name: batch-photo-editing +description: "Apply a specified edit, background removal or upscaling task to a list of local images with DreamLayer. Use for a bounded batch with per-file results and a total credit budget." +--- + +# batch photo editing + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Enumerate input files and assign unique new outputs before uploading. Estimate total operations from current capabilities; an edit plus cutout is two paid operations per image. Maintain a per-item ledger of input, asset ID, saved request, idempotency key, execution ID, status and output. Process sequentially initially; stop new submissions on insufficient credits or exhausted budget. Resume existing executions after interruption. Skip already completed outputs rather than changing paths and charging again. Inspect a sample first and all outputs for tasks where fidelity matters. The batch is orchestration over individual operations, not a separate DreamLayer batch endpoint. + diff --git a/.github/skills/batch-photo-editing/references/workflow-contract.md b/.github/skills/batch-photo-editing/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.github/skills/batch-photo-editing/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.github/skills/generate-or-edit-image/SKILL.md b/.github/skills/generate-or-edit-image/SKILL.md new file mode 100644 index 0000000..82e3df5 --- /dev/null +++ b/.github/skills/generate-or-edit-image/SKILL.md @@ -0,0 +1,11 @@ +--- +name: generate-or-edit-image +description: "Generate an original image, edit a local reference, remove a background, or upscale an image with DreamLayer. Use for visual assets in an app, website or creative project when the user wants image files." +--- + +# generate or edit image + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Establish the visual brief, output count, aspect ratio, destination and authorized credits. Select the operation by the requested change; an edit needs a reference, a new image does not. Use background_remove for transparency rather than assuming a prompt creates alpha. Upscale doubles both dimensions; input longest side must be <=2048 and output is capped at 4096. Supported aspect ratios: 1:1, 16:9, 9:16, 4:3, 3:4. Save files into the project, inspect, and report dimensions and remaining limitations. For logos, product photos, marketing and print artwork, read the matching installed task skill if present. + diff --git a/.github/skills/generate-or-edit-image/references/workflow-contract.md b/.github/skills/generate-or-edit-image/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.github/skills/generate-or-edit-image/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.github/skills/godot-sprite-import/SKILL.md b/.github/skills/godot-sprite-import/SKILL.md new file mode 100644 index 0000000..e35e069 --- /dev/null +++ b/.github/skills/godot-sprite-import/SKILL.md @@ -0,0 +1,11 @@ +--- +name: godot-sprite-import +description: "Import an existing DreamLayer sprite ZIP into a Godot 4 demo and play the animation. Use when the user already has generated frames and wants a runnable game-engine example." +--- + +# godot sprite import + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +This is local import and spends no credits. Use scripts/import_sprite.py with --engine godot --zip /absolute/walk.zip --out /absolute/new-demo. It reads PNG frames and atlas durations from the bundle, validates them and creates a Godot 4 project with AnimatedSprite2D. Run godot --headless --path /absolute/new-demo --editor --quit, then open the project and inspect playback. Preserve returned frame durations; do not assume constant FPS. Use the generated demo as a reference when importing into an existing project, keeping existing settings intact. If Godot is unavailable, mark engine validation pending; successful file generation alone does not prove engine playback. + diff --git a/.github/skills/godot-sprite-import/references/workflow-contract.md b/.github/skills/godot-sprite-import/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.github/skills/godot-sprite-import/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.github/skills/godot-sprite-import/scripts/import_sprite.py b/.github/skills/godot-sprite-import/scripts/import_sprite.py new file mode 100644 index 0000000..217d493 --- /dev/null +++ b/.github/skills/godot-sprite-import/scripts/import_sprite.py @@ -0,0 +1,168 @@ +"""Import a DreamLayer ZIP into a new Godot/Unity example. No API calls or dependencies.""" +import argparse, json, math, re, struct, zipfile +from pathlib import Path, PurePosixPath + +GODOT_SCRIPT = '''extends Node2D +var actor: AnimatedSprite2D +func _ready() -> void: + var data = JSON.parse_string(FileAccess.get_file_as_string("res://frames/manifest.json")) + var animation := SpriteFrames.new() + animation.add_animation("dreamlayer") + animation.set_animation_speed("dreamlayer", 1.0) + animation.set_animation_loop("dreamlayer", data.loop) + for i in range(data.frames.size()): + var texture = load("res://frames/" + data.frames[i] + ".png") + assert(texture != null, "Missing sprite texture") + animation.add_frame("dreamlayer", texture, data.durations_ms[i] / 1000.0) + actor = AnimatedSprite2D.new() + actor.sprite_frames = animation + actor.position = Vector2(320, 220) + actor.scale = Vector2(2, 2) + add_child(actor) + actor.play("dreamlayer") + if "--smoke" in OS.get_cmdline_user_args(): + var before := actor.frame + await get_tree().create_timer(0.9).timeout + assert(actor.is_playing(), "Animation stopped") + assert(actor.frame != before, "Animation did not advance") + print("DREAMLAYER_IMPORT_OK frames=", animation.get_frame_count("dreamlayer")) + get_tree().quit() +''' + +UNITY_PLAYER = '''using System; +using UnityEngine; +[RequireComponent(typeof(SpriteRenderer))] +public class DreamLayerFramePlayer : MonoBehaviour { + [Serializable] public class Manifest { + public string[] frames; + public float[] durations_ms; + public bool loop; + } + private Sprite[] frames; + private Manifest manifest; + private SpriteRenderer target; + private int index; + private float elapsed; + private bool finished; + void Start() { + var text = Resources.Load("DreamLayerDemo/manifest"); + if (text == null) { Debug.LogError("Missing DreamLayer manifest"); enabled=false; return; } + manifest = JsonUtility.FromJson(text.text); + if (manifest.frames == null || manifest.frames.Length == 0 || manifest.durations_ms == null || manifest.frames.Length != manifest.durations_ms.Length) { + Debug.LogError("Invalid DreamLayer timing manifest"); enabled=false; return; + } + target = GetComponent(); + frames = new Sprite[manifest.frames.Length]; + for(int i=0;i("DreamLayerDemo/"+manifest.frames[i]); + if(frames[i]==null || manifest.durations_ms[i]<=0) { Debug.LogError("Invalid DreamLayer frame"); enabled=false; return; } + } + target.sprite=frames[0]; + } + void Update() { + if(finished || frames==null) return; + elapsed+=Time.deltaTime; + while(elapsed>=manifest.durations_ms[index]/1000f) { + elapsed-=manifest.durations_ms[index]/1000f; + if(index==frames.Length-1 && !manifest.loop) { finished=true; return; } + index=(index+1)%frames.Length; + target.sprite=frames[index]; + } + } +} +''' + +UNITY_EDITOR = '''using UnityEditor; +using UnityEngine; +public class DreamLayerSpriteImporter : AssetPostprocessor { + void OnPreprocessTexture() { + if(!assetPath.StartsWith("Assets/DreamLayerDemo/Resources/DreamLayerDemo/")) return; + var importer=(TextureImporter)assetImporter; + importer.textureType=TextureImporterType.Sprite; + importer.spriteImportMode=SpriteImportMode.Single; + importer.spritePixelsPerUnit=128; + importer.alphaIsTransparency=true; + importer.mipmapEnabled=false; + importer.textureCompression=TextureImporterCompression.Uncompressed; + } + [MenuItem("Tools/DreamLayer/Create sprite demo")] + public static void CreateDemo() { + var actor=new GameObject("DreamLayer sprite demo"); + Undo.RegisterCreatedObjectUndo(actor,"Create DreamLayer demo"); + actor.AddComponent(); + actor.AddComponent(); + if(Camera.main==null) { + var cameraObject=new GameObject("DreamLayer demo camera"); + Undo.RegisterCreatedObjectUndo(cameraObject,"Create DreamLayer camera"); + cameraObject.tag="MainCamera"; + var camera=cameraObject.AddComponent(); + camera.orthographic=true; camera.orthographicSize=1.5f; + cameraObject.transform.position=new Vector3(0,0,-10); + } + Selection.activeGameObject=actor; + } +} +''' + +def read_bundle(path): + with zipfile.ZipFile(path) as z: + infos=z.infolist() + if len(infos)>150 or sum(i.file_size for i in infos)>200*1024*1024: + raise ValueError('Bundle exceeds import limits') + names=[i.filename for i in infos] + if len(set(names))!=len(names): raise ValueError('Duplicate ZIP entries') + for name in names: + p=PurePosixPath(name) + if p.is_absolute() or '..' in p.parts or '\\' in name: + raise ValueError('Unsafe ZIP path') + atlas=json.loads(z.read('atlas.json')) + if atlas.get('schema_version')!=1: raise ValueError('Unsupported atlas schema') + order=list(atlas['frames']) + if not 7<=len(order)<=100 or atlas['frame_count']!=len(order): + raise ValueError('Frame count mismatch') + durations=atlas.get('frame_durations_ms') + if not isinstance(durations,list) or len(durations)!=len(order): + raise ValueError('Missing per-frame durations') + if any(type(t) not in (int,float) or not math.isfinite(t) or t<=0 for t in durations): + raise ValueError('Invalid duration') + frames={} + for name in order: + if not re.fullmatch(r'[A-Za-z0-9_-]+',name): raise ValueError('Unsafe frame name') + data=z.read('frames/'+name+'.png') + if len(data)<33 or data[:8]!=b'\x89PNG\r\n\x1a\n': raise ValueError('Invalid PNG') + width,height=struct.unpack('>II',data[16:24]) + if width!=atlas['cell'] or height!=atlas['cell']: raise ValueError('Canvas mismatch') + if data[25] not in (4,6): raise ValueError('PNG lacks an alpha channel') + frames[name]=data + manifest={'frames':order,'durations_ms':durations, + 'loop':atlas.get('animation_mode','loop' if atlas.get('action') in ('walk','run','idle') else 'once')=='loop', + 'width':atlas['cell'],'height':atlas['cell']} + return frames,manifest + +def build(bundle,out,engine): + frames,manifest=read_bundle(bundle) + out=Path(out) + if out.exists(): raise ValueError('Destination must be new; refusing overwrite') + out.mkdir(parents=True) + assets=out/'frames' if engine=='godot' else out/'Assets/DreamLayerDemo/Resources/DreamLayerDemo' + assets.mkdir(parents=True) + for name,data in frames.items(): (assets/(name+'.png')).write_bytes(data) + (assets/'manifest.json').write_text(json.dumps(manifest,indent=2)+'\n') + if engine=='godot': + (out/'project.godot').write_text('config_version=5\n[application]\nconfig/name="DreamLayer sprite demo"\nrun/main_scene="res://main.tscn"\n[display]\nwindow/size/viewport_width=640\nwindow/size/viewport_height=440\n[rendering]\nrenderer/rendering_method="gl_compatibility"\n') + (out/'main.tscn').write_text('[gd_scene load_steps=2 format=3]\n[ext_resource type="Script" path="res://main.gd" id="1"]\n[node name="DreamLayerDemo" type="Node2D"]\nscript = ExtResource("1")\n') + (out/'main.gd').write_text(GODOT_SCRIPT) + (out/'.gitignore').write_text('.godot/\n') + else: + base=out/'Assets/DreamLayerDemo' + (base/'Editor').mkdir() + (base/'DreamLayerFramePlayer.cs').write_text(UNITY_PLAYER) + (base/'Editor/DreamLayerSpriteImporter.cs').write_text(UNITY_EDITOR) + print(json.dumps({'engine':engine,'frames':len(frames),'duration_ms':sum(manifest['durations_ms']),'output':str(out)})) + +if __name__=='__main__': + p=argparse.ArgumentParser(description=__doc__) + p.add_argument('--engine',choices=['godot','unity'],required=True) + p.add_argument('--zip',type=Path,required=True) + p.add_argument('--out',type=Path,required=True) + a=p.parse_args();build(a.zip,a.out,a.engine) diff --git a/.github/skills/logo-and-app-icon-concepts/SKILL.md b/.github/skills/logo-and-app-icon-concepts/SKILL.md new file mode 100644 index 0000000..4c577b8 --- /dev/null +++ b/.github/skills/logo-and-app-icon-concepts/SKILL.md @@ -0,0 +1,11 @@ +--- +name: logo-and-app-icon-concepts +description: "Create logo concepts, brand marks or app-icon artwork as raster images with DreamLayer. Use when building a brand or app that needs original visual directions and PNG assets." +--- + +# logo and app icon concepts + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Collect brand name, audience, tone, silhouette and palette; use a small explicitly authorized number of concepts. Request original marks without copying an existing brand. Prefer symbol exploration first; add precise lettering separately if generation misspells it. Generate a square icon concept and inspect it at small size. Remove background in a separate authorized operation if required. Export raster images and explain whether alpha is present. Do not describe a PNG as SVG, a scalable vector master, a cleared trademark or exclusive ownership. A supplied mark should be edited only as requested, preserving its identity. + diff --git a/.github/skills/logo-and-app-icon-concepts/references/workflow-contract.md b/.github/skills/logo-and-app-icon-concepts/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.github/skills/logo-and-app-icon-concepts/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.github/skills/marketing-image-variations/SKILL.md b/.github/skills/marketing-image-variations/SKILL.md new file mode 100644 index 0000000..bcfe173 --- /dev/null +++ b/.github/skills/marketing-image-variations/SKILL.md @@ -0,0 +1,11 @@ +--- +name: marketing-image-variations +description: "Generate ad, banner and social campaign image variations from a brief or reference with DreamLayer. Use when the user needs creative files for a campaign or content calendar." +--- + +# marketing image variations + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Define audience, offer, destination placement, brand colors, required copy, aspect ratio and authorized variant count. Map output ratios to the supported ratios; use an explicit crop/resize step when a placement needs exact pixels. Generate or edit one approved reference direction and inspect each variation. Check text and product claims. For precise typography, use generated imagery as a background and add text in the project's design/code tools. Save an output manifest with brief, filename, dimensions and review status. No ad account upload, spend or campaign launch is implied. + diff --git a/.github/skills/marketing-image-variations/references/workflow-contract.md b/.github/skills/marketing-image-variations/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.github/skills/marketing-image-variations/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.github/skills/print-on-demand-artwork/SKILL.md b/.github/skills/print-on-demand-artwork/SKILL.md new file mode 100644 index 0000000..ffcee32 --- /dev/null +++ b/.github/skills/print-on-demand-artwork/SKILL.md @@ -0,0 +1,11 @@ +--- +name: print-on-demand-artwork +description: "Create raster artwork concepts for shirts, stickers, posters and print-on-demand products using DreamLayer. Use when a seller needs original artwork and transparent PNG preparation." +--- + +# print on demand artwork + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Ask for intended physical size and the print provider's pixel requirements when the user needs final production files. Generate artwork concepts, with background_remove as a separate operation if transparency is needed. Inspect actual pixel dimensions and alpha; calculate effective PPI as pixels divided by inches. Do not label the output print-ready until it meets the specific supplier's requirements. Upscaling does not guarantee original detail. Do not promise vectors, seamless repeat patterns, embroidery files, color-profile conversion or store publishing. If requirements exceed supported output, provide the concept and state the remaining production step. + diff --git a/.github/skills/print-on-demand-artwork/references/workflow-contract.md b/.github/skills/print-on-demand-artwork/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.github/skills/print-on-demand-artwork/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.github/skills/product-image-generation/SKILL.md b/.github/skills/product-image-generation/SKILL.md new file mode 100644 index 0000000..88ce976 --- /dev/null +++ b/.github/skills/product-image-generation/SKILL.md @@ -0,0 +1,11 @@ +--- +name: product-image-generation +description: "Create ecommerce product-photo backgrounds and lifestyle image concepts from a supplied product photo using DreamLayer. Use when preparing store images or product campaign visuals." +--- + +# product image generation + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Require a reference photo for a real product. Identify the product details that must remain: shape, label, logo, color, count and proportions. Use image_to_image with a specific setting and composition; use background_remove for a clean cutout. Inspect the result against the reference and flag altered details before it is used in a listing. DreamLayer generation is not a guarantee of SKU fidelity. Export local files; do not publish listings or claim storefront integration. Generate one reviewable candidate before expanding an authorized batch. + diff --git a/.github/skills/product-image-generation/references/workflow-contract.md b/.github/skills/product-image-generation/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.github/skills/product-image-generation/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.github/skills/sprite-sheet-from-reference/SKILL.md b/.github/skills/sprite-sheet-from-reference/SKILL.md new file mode 100644 index 0000000..1f946dc --- /dev/null +++ b/.github/skills/sprite-sheet-from-reference/SKILL.md @@ -0,0 +1,11 @@ +--- +name: sprite-sheet-from-reference +description: "Turn one character, object or effect reference into an animated sprite-sheet ZIP with transparent PNG frames using DreamLayer. Use for game sprites, walk cycles, idle animations or custom motion within a credit budget." +--- + +# sprite sheet from reference + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +Read the workflow contract, then current sprite_pricing in capabilities. Exactly one reference and one of action (walk/run/idle) or animation_prompt (1–4000 chars) are required. Choose frame_count 7–100 and square frame_size 32,64,128,256,512,720,1080. action defaults to loop; custom animation defaults to once; explicitly set animation_mode when needed. Quote in integer cents: min(n,14)*14 + max(n-14,0)*7, divide by 17 cents per credit, round the WHOLE order upward once to 0.1 credit. Treat these as the current example values, and recompute from live capabilities. 7 frames currently cost 5.8 credits; 12 cost 9.9. Set max_credits within the user's authorized cap. Compare to available balance, not the separately rounded funding buckets. Sprites have no customer cancellation. Save execution and poll/resume; failed/expired work restores the hold. Download .zip, inspect its manifest and preview, and validate frame count/alpha/timing. Do not claim pixel-grid preservation, perfect anatomy, eight directions, editable Aseprite layers or production quality. For engine import use the matching engine skill. CLI: dreamlayer sprite reference.png --action walk --frames 7 --frame-size 128 --max-credits 5.8 --out walk.zip --json + diff --git a/.github/skills/sprite-sheet-from-reference/references/workflow-contract.md b/.github/skills/sprite-sheet-from-reference/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.github/skills/sprite-sheet-from-reference/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.github/skills/unity-sprite-import/SKILL.md b/.github/skills/unity-sprite-import/SKILL.md new file mode 100644 index 0000000..b66536b --- /dev/null +++ b/.github/skills/unity-sprite-import/SKILL.md @@ -0,0 +1,11 @@ +--- +name: unity-sprite-import +description: "Prepare an existing DreamLayer sprite ZIP for Unity 2D with a local frame player and texture importer. Use when a Unity project needs the downloaded animation imported and played." +--- + +# unity sprite import + +Read [the workflow contract](references/workflow-contract.md) for setup, billing, recovery and output checks. + +This is local import and spends no credits. Use scripts/import_sprite.py with --engine unity --zip /absolute/walk.zip --out /absolute/new-demo. It generates an Assets/DreamLayerDemo folder, sprite import settings, a C# frame player with each frame's timing, and an Editor menu to create the demo. Copy Assets into a Unity project, wait for script compilation, then run Tools > DreamLayer > Create sprite demo and press Play. Do not overwrite existing assets without checking the destination. If the Unity editor is unavailable, mark compile/playback validation pending. This is an ordinary Unity import example, not a claim of inclusion in Unity's official plugin. + diff --git a/.github/skills/unity-sprite-import/references/workflow-contract.md b/.github/skills/unity-sprite-import/references/workflow-contract.md new file mode 100644 index 0000000..8d37476 --- /dev/null +++ b/.github/skills/unity-sprite-import/references/workflow-contract.md @@ -0,0 +1,46 @@ +# DreamLayer workflow contract + +Use the installed DreamLayer MCP tools, or the pinned CLI when MCP is unavailable. +Read the actual tool schema before calling. Never pass fields a client does not expose. + +## Setup +Requires Node.js >=22.12 and a DreamLayer API key from https://platform.dreamlayer.io. +Install the supported sprite-capable beta: `npm install -g dreamlayer@0.4.0-beta.3`. +MCP: `npx -y @dreamlayer/mcp@0.4.0-beta.4`. Supply DREAMLAYER_API_KEY to the server process using the host's secret settings. Never commit keys or put them in prompts. +The stable npm tag remains 0.3.0. Do not claim it supports this complete workflow. + +## Before paid work +1. Read dreamlayer_capabilities and dreamlayer_balance (or CLI capabilities/balance --json). If either fails, resolve it before spending. +2. Confirm the requested operation is advertised. Ordinary completed image operations currently cost one credit each; obtain the live cost from capabilities. A multi-step edit and cutout costs two operations. +3. Use the user's already-authorized operation, number of outputs and credit budget. Ask only if missing. Never buy credits or expand the batch implicitly. +4. For file operations, verify input readability and a new output path before submitting. Upload once using dreamlayer_upload_image; preserve its input_asset_id. +5. Save a unique idempotency_key and the complete request locally before each generation. Do not store credentials in the job record. + +## Execute and recover +Select operation explicitly: text_to_image; image_to_image; background_remove; upscale; sprite_sheet. +All except text_to_image require input_asset_id. Generate returns execution_id, status, asset, question and last_event_id. +Preserve IDs/cursor after each response. For running work, call dreamlayer_execution or dreamlayer_events; do not generate a replacement after a timeout. +A needs_input result is a question, not a failed generation. Continue using conversation_id and respond. +For completed work, dreamlayer_download writes an absolute new local path. Never treat a returned asset URL as proof that the local file exists. +On a local download error, repair the destination and download the same execution. +File-based CLI retries upload a new asset; use status and download after uncertainty rather than rerunning the paid command. +Stop at insufficient credits, invalid request, unsupported operation, or an unresolved contract error. Keep recoverable state; do not loop paid retries. + +## Output acceptance +Inspect the saved image or preview. Check dimensions, transparency if requested, text spelling, reference fidelity and the intended use. Label uninspected outputs. +PNG output does not establish SVG/vector structure, pixel-perfect art, trademark clearance, print compliance, exact product fidelity or consistent animation. +API documentation: https://docs.dreamlayer.io/agent-api +Recovery: https://docs.dreamlayer.io/agent-api/jobs-and-events + +## CLI examples +```sh +dreamlayer capabilities --json +dreamlayer balance --json +dreamlayer generate "Original image brief" --aspect 1:1 --idempotency-key UNIQUE_SAVED_KEY --out concept.png --json +dreamlayer edit reference.png "Change the background to a simple studio scene" --idempotency-key UNIQUE_SAVED_KEY --out edited.png --json +dreamlayer cutout reference.png --idempotency-key UNIQUE_SAVED_KEY --out cutout.png --json +dreamlayer status EXECUTION_ID --json +dreamlayer download EXECUTION_ID --out recovered.png --json +``` +Placeholder IDs and keys must be replaced. Check CLI --help for supported flags; --max-credits is for sprite jobs. + diff --git a/.github/skills/unity-sprite-import/scripts/import_sprite.py b/.github/skills/unity-sprite-import/scripts/import_sprite.py new file mode 100644 index 0000000..217d493 --- /dev/null +++ b/.github/skills/unity-sprite-import/scripts/import_sprite.py @@ -0,0 +1,168 @@ +"""Import a DreamLayer ZIP into a new Godot/Unity example. No API calls or dependencies.""" +import argparse, json, math, re, struct, zipfile +from pathlib import Path, PurePosixPath + +GODOT_SCRIPT = '''extends Node2D +var actor: AnimatedSprite2D +func _ready() -> void: + var data = JSON.parse_string(FileAccess.get_file_as_string("res://frames/manifest.json")) + var animation := SpriteFrames.new() + animation.add_animation("dreamlayer") + animation.set_animation_speed("dreamlayer", 1.0) + animation.set_animation_loop("dreamlayer", data.loop) + for i in range(data.frames.size()): + var texture = load("res://frames/" + data.frames[i] + ".png") + assert(texture != null, "Missing sprite texture") + animation.add_frame("dreamlayer", texture, data.durations_ms[i] / 1000.0) + actor = AnimatedSprite2D.new() + actor.sprite_frames = animation + actor.position = Vector2(320, 220) + actor.scale = Vector2(2, 2) + add_child(actor) + actor.play("dreamlayer") + if "--smoke" in OS.get_cmdline_user_args(): + var before := actor.frame + await get_tree().create_timer(0.9).timeout + assert(actor.is_playing(), "Animation stopped") + assert(actor.frame != before, "Animation did not advance") + print("DREAMLAYER_IMPORT_OK frames=", animation.get_frame_count("dreamlayer")) + get_tree().quit() +''' + +UNITY_PLAYER = '''using System; +using UnityEngine; +[RequireComponent(typeof(SpriteRenderer))] +public class DreamLayerFramePlayer : MonoBehaviour { + [Serializable] public class Manifest { + public string[] frames; + public float[] durations_ms; + public bool loop; + } + private Sprite[] frames; + private Manifest manifest; + private SpriteRenderer target; + private int index; + private float elapsed; + private bool finished; + void Start() { + var text = Resources.Load("DreamLayerDemo/manifest"); + if (text == null) { Debug.LogError("Missing DreamLayer manifest"); enabled=false; return; } + manifest = JsonUtility.FromJson(text.text); + if (manifest.frames == null || manifest.frames.Length == 0 || manifest.durations_ms == null || manifest.frames.Length != manifest.durations_ms.Length) { + Debug.LogError("Invalid DreamLayer timing manifest"); enabled=false; return; + } + target = GetComponent(); + frames = new Sprite[manifest.frames.Length]; + for(int i=0;i("DreamLayerDemo/"+manifest.frames[i]); + if(frames[i]==null || manifest.durations_ms[i]<=0) { Debug.LogError("Invalid DreamLayer frame"); enabled=false; return; } + } + target.sprite=frames[0]; + } + void Update() { + if(finished || frames==null) return; + elapsed+=Time.deltaTime; + while(elapsed>=manifest.durations_ms[index]/1000f) { + elapsed-=manifest.durations_ms[index]/1000f; + if(index==frames.Length-1 && !manifest.loop) { finished=true; return; } + index=(index+1)%frames.Length; + target.sprite=frames[index]; + } + } +} +''' + +UNITY_EDITOR = '''using UnityEditor; +using UnityEngine; +public class DreamLayerSpriteImporter : AssetPostprocessor { + void OnPreprocessTexture() { + if(!assetPath.StartsWith("Assets/DreamLayerDemo/Resources/DreamLayerDemo/")) return; + var importer=(TextureImporter)assetImporter; + importer.textureType=TextureImporterType.Sprite; + importer.spriteImportMode=SpriteImportMode.Single; + importer.spritePixelsPerUnit=128; + importer.alphaIsTransparency=true; + importer.mipmapEnabled=false; + importer.textureCompression=TextureImporterCompression.Uncompressed; + } + [MenuItem("Tools/DreamLayer/Create sprite demo")] + public static void CreateDemo() { + var actor=new GameObject("DreamLayer sprite demo"); + Undo.RegisterCreatedObjectUndo(actor,"Create DreamLayer demo"); + actor.AddComponent(); + actor.AddComponent(); + if(Camera.main==null) { + var cameraObject=new GameObject("DreamLayer demo camera"); + Undo.RegisterCreatedObjectUndo(cameraObject,"Create DreamLayer camera"); + cameraObject.tag="MainCamera"; + var camera=cameraObject.AddComponent(); + camera.orthographic=true; camera.orthographicSize=1.5f; + cameraObject.transform.position=new Vector3(0,0,-10); + } + Selection.activeGameObject=actor; + } +} +''' + +def read_bundle(path): + with zipfile.ZipFile(path) as z: + infos=z.infolist() + if len(infos)>150 or sum(i.file_size for i in infos)>200*1024*1024: + raise ValueError('Bundle exceeds import limits') + names=[i.filename for i in infos] + if len(set(names))!=len(names): raise ValueError('Duplicate ZIP entries') + for name in names: + p=PurePosixPath(name) + if p.is_absolute() or '..' in p.parts or '\\' in name: + raise ValueError('Unsafe ZIP path') + atlas=json.loads(z.read('atlas.json')) + if atlas.get('schema_version')!=1: raise ValueError('Unsupported atlas schema') + order=list(atlas['frames']) + if not 7<=len(order)<=100 or atlas['frame_count']!=len(order): + raise ValueError('Frame count mismatch') + durations=atlas.get('frame_durations_ms') + if not isinstance(durations,list) or len(durations)!=len(order): + raise ValueError('Missing per-frame durations') + if any(type(t) not in (int,float) or not math.isfinite(t) or t<=0 for t in durations): + raise ValueError('Invalid duration') + frames={} + for name in order: + if not re.fullmatch(r'[A-Za-z0-9_-]+',name): raise ValueError('Unsafe frame name') + data=z.read('frames/'+name+'.png') + if len(data)<33 or data[:8]!=b'\x89PNG\r\n\x1a\n': raise ValueError('Invalid PNG') + width,height=struct.unpack('>II',data[16:24]) + if width!=atlas['cell'] or height!=atlas['cell']: raise ValueError('Canvas mismatch') + if data[25] not in (4,6): raise ValueError('PNG lacks an alpha channel') + frames[name]=data + manifest={'frames':order,'durations_ms':durations, + 'loop':atlas.get('animation_mode','loop' if atlas.get('action') in ('walk','run','idle') else 'once')=='loop', + 'width':atlas['cell'],'height':atlas['cell']} + return frames,manifest + +def build(bundle,out,engine): + frames,manifest=read_bundle(bundle) + out=Path(out) + if out.exists(): raise ValueError('Destination must be new; refusing overwrite') + out.mkdir(parents=True) + assets=out/'frames' if engine=='godot' else out/'Assets/DreamLayerDemo/Resources/DreamLayerDemo' + assets.mkdir(parents=True) + for name,data in frames.items(): (assets/(name+'.png')).write_bytes(data) + (assets/'manifest.json').write_text(json.dumps(manifest,indent=2)+'\n') + if engine=='godot': + (out/'project.godot').write_text('config_version=5\n[application]\nconfig/name="DreamLayer sprite demo"\nrun/main_scene="res://main.tscn"\n[display]\nwindow/size/viewport_width=640\nwindow/size/viewport_height=440\n[rendering]\nrenderer/rendering_method="gl_compatibility"\n') + (out/'main.tscn').write_text('[gd_scene load_steps=2 format=3]\n[ext_resource type="Script" path="res://main.gd" id="1"]\n[node name="DreamLayerDemo" type="Node2D"]\nscript = ExtResource("1")\n') + (out/'main.gd').write_text(GODOT_SCRIPT) + (out/'.gitignore').write_text('.godot/\n') + else: + base=out/'Assets/DreamLayerDemo' + (base/'Editor').mkdir() + (base/'DreamLayerFramePlayer.cs').write_text(UNITY_PLAYER) + (base/'Editor/DreamLayerSpriteImporter.cs').write_text(UNITY_EDITOR) + print(json.dumps({'engine':engine,'frames':len(frames),'duration_ms':sum(manifest['durations_ms']),'output':str(out)})) + +if __name__=='__main__': + p=argparse.ArgumentParser(description=__doc__) + p.add_argument('--engine',choices=['godot','unity'],required=True) + p.add_argument('--zip',type=Path,required=True) + p.add_argument('--out',type=Path,required=True) + a=p.parse_args();build(a.zip,a.out,a.engine) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 2044dc6..5873ba3 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -10,5 +10,6 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.12' + - run: python3 scripts/sync_skills.py --check - run: python3 scripts/validate.py - run: python3 -m unittest discover -s tests -v diff --git a/README.md b/README.md index 3f408b0..200bea0 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ Use DreamLayer for logo and app-icon concepts, product imagery, marketing visual npx skills add TheDesignFounder/dreamlayer-agent-plugin ``` -Choose the workflows you need. The canonical source is `.agents/skills/`. Installing skills provides instructions; configure DreamLayer separately with an API key and credits. +Choose the workflows you need. The canonical source is `plugins/dreamlayer/skills/`; every host directory is generated from it. Installing skills provides instructions; configure DreamLayer separately with an API key and credits. ## Connect the tools Requires Node.js 22.12 or later. Get your key at [DreamLayer Platform](https://platform.dreamlayer.io/?utm_source=github&utm_medium=agent_distribution&utm_campaign=agent_launch&utm_content=readme). Use `@dreamlayer/mcp@0.4.0-beta.4` for this full workflow set. The stable 0.3.0 tag does not cover sprites. -See [client setup](docs/client-setup.md) for Claude Code, Codex, Cursor, OpenCode, Copilot and Gemini CLI. +See [client setup](docs/client-setup.md) for Claude Code, Codex, Cursor, OpenCode, Gemini CLI and OpenClaw. GitHub Copilot and Cline have their own pages: [Copilot](docs/copilot-setup.md), [Cline](docs/cline-setup.md). ## What you can make | Task | Workflow | Output and scope | @@ -35,7 +35,16 @@ Read capabilities and balance before paid work. Standard image operations curren Interrupted work resumes from its saved execution ID. Downloading a completed job does not start a replacement generation. [Workflow contract](docs/workflow-contract.md) · [API documentation](https://docs.dreamlayer.io/agent-api) ## Maintain one source -Edit `.agents/skills/`, then run `python3 scripts/build_plugins.py` to regenerate the plugin copies. Run `python3 scripts/validate.py` before publishing. Generated copies must match the canonical skills. +Edit `plugins/dreamlayer/skills/`, then run `python3 scripts/sync_skills.py` to regenerate every host copy. Run `python3 scripts/validate.py` before publishing; it fails when a copy has drifted. + +| Path | Read by | +| --- | --- | +| plugins/dreamlayer/skills/ | Claude Code and Codex plugins; the canonical source | +| .agents/skills/ | Cursor, OpenCode, Copilot, and `npx skills` installs | +| .github/skills/ | GitHub Copilot in the CLI, VS Code and the coding agent | +| .cline/skills/ | Cline | + +Nothing is symlinked, so the layout survives Windows checkouts and archive downloads. [Support](mailto:Mackenzie@dreamlayer.io) · [Terms](https://dreamlayer.io/terms) · [Privacy](https://dreamlayer.io/privacy) diff --git a/docs/client-setup.md b/docs/client-setup.md index 5156d8d..69f49c3 100644 --- a/docs/client-setup.md +++ b/docs/client-setup.md @@ -21,7 +21,7 @@ The repository marketplace packages the same nine skills and pinned MCP. OpenAI npx skills add TheDesignFounder/dreamlayer-agent-plugin --list npx skills add TheDesignFounder/dreamlayer-agent-plugin ``` -Select your agent and desired skills. Alternatively copy whole folders from .agents/skills into your project's .agents/skills. References and scripts must travel with SKILL.md. +Select your agent and desired skills. Alternatively copy whole folders out of this repository into the path your host reads: `.agents/skills/` for Cursor and OpenCode, `.github/skills/` for Copilot, `.cline/skills/` for Cline. All three are generated from `plugins/dreamlayer/skills/` and are byte identical, so pick whichever your host loads. References and scripts must travel with SKILL.md. ## Cursor Use .cursor/mcp.json with a private key supplied through the client's secret configuration. This placeholder is not a working key: @@ -36,11 +36,23 @@ Use opencode.json: ``` ## GitHub Copilot in VS Code -Use .vscode/mcp.json; the password input keeps the actual key out of source control: +Full instructions, including the CLI and the `.github/skills/` project path, are in [Copilot setup](copilot-setup.md). Use .vscode/mcp.json; the password input keeps the actual key out of source control: ```json {"inputs":[{"type":"promptString","id":"dreamlayer-api-key","description":"DreamLayer API key","password":true}],"servers":{"dreamlayer":{"type":"stdio","command":"npx","args":["-y","@dreamlayer/mcp@0.4.0-beta.4"],"env":{"DREAMLAYER_API_KEY":"${input:dreamlayer-api-key}"}}}} ``` +## Cline + +Skills live in `.cline/skills/` in the project or `~/.cline/skills/` globally, and the MCP +server goes in the CLI settings file that `cline config mcp` prints. Full instructions are in +[Cline setup](cline-setup.md). + +```sh +cline skill add TheDesignFounder/dreamlayer-agent-plugin -a cline -s '*' +cline config skills +cline config mcp +``` + ## Gemini CLI Use mcpServers in .gemini/settings.json with command npx and args ["-y","@dreamlayer/mcp@0.4.0-beta.4"]. Supply DREAMLAYER_API_KEY through the process environment or private user settings. @@ -65,4 +77,4 @@ removes the wait. ## Verify List tools and read capabilities and balance. These spend nothing. Confirm sprite_sheet is advertised. A stale installed MCP may fail while the current beta works: update and restart the process. Stable 0.3.0 does not support this full workflow. -Sources: [Cursor](https://cursor.com/docs/skills), [OpenCode](https://opencode.ai/docs/mcp-servers/), [Copilot](https://docs.github.com/en/copilot/concepts/agents/about-agent-skills), [VS Code](https://code.visualstudio.com/docs/copilot/customization/mcp-servers), [Gemini](https://geminicli.com/docs/cli/skills/), [Codex](https://developers.openai.com/plugins/build/plugins). +Sources: [Cursor](https://cursor.com/docs/context/skills), [OpenCode](https://opencode.ai/docs/skills/), [Copilot](https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/customize-cloud-agent/add-skills), [VS Code](https://code.visualstudio.com/docs/copilot/customization/mcp-servers), [Cline](https://docs.cline.bot/features/skills), [Gemini](https://geminicli.com/docs/cli/skills/), [Codex](https://developers.openai.com/plugins/build/plugins). diff --git a/docs/cline-setup.md b/docs/cline-setup.md new file mode 100644 index 0000000..5aefcae --- /dev/null +++ b/docs/cline-setup.md @@ -0,0 +1,96 @@ +# Cline setup + +Use DreamLayer when a task needs actual image files: an original image, a raster logo or app-icon concept, product and marketing visuals, print-on-demand artwork, a background removal or upscale, a bounded batch of photo edits, or a sprite-sheet animation from one reference. Cline keeps writing the code; DreamLayer produces the assets the code ships. + +## Install the skills + +This repository carries the nine workflows in `.cline/skills/`, the project path Cline recommends and expects teams to commit. + +```sh +git clone https://github.com/TheDesignFounder/dreamlayer-agent-plugin.git +cp -R dreamlayer-agent-plugin/.cline/skills/. your-project/.cline/skills/ +``` + +For skills that follow you between projects, copy the same folders into `~/.cline/skills/` on macOS and Linux, or `%USERPROFILE%\.cline\skills\` on Windows. A global skill wins over a project skill with the same name. + +The installer route writes `.agents/skills/` instead, which Cline also loads: + +```sh +cline skill add TheDesignFounder/dreamlayer-agent-plugin -a cline -s '*' +``` + +Confirm discovery from your project root: + +```sh +cline config skills +``` + +Nine DreamLayer entries appear under `Enabled skills:` with their paths. Pass the section name, +because plain `cline config` opens an interactive view and needs a terminal. In the IDE extension, open the Skills tab from the scale icon at the bottom of the Cline panel. + +## The nine workflows + +| Workflow | Makes | +| --- | --- | +| generate-or-edit-image | An original image, an edit, a cutout or an upscale | +| logo-and-app-icon-concepts | Raster logo and app-icon directions, not vector masters | +| product-image-generation | Ecommerce backgrounds and lifestyle concepts from a product photo | +| print-on-demand-artwork | Shirt, sticker and poster artwork with transparent PNG preparation | +| marketing-image-variations | Ad, banner and social variations from a brief or reference | +| batch-photo-editing | One edit, cutout or upscale applied across a list of files, with a budget | +| sprite-sheet-from-reference | A sprite-sheet ZIP of transparent frames, atlas and preview | +| godot-sprite-import | A runnable Godot 4 demo from a downloaded sprite ZIP | +| unity-sprite-import | Unity 2D import files and a frame player from a downloaded sprite ZIP | + +## Connect the tools + +Requires Node.js 22.12 or later and a key from [DreamLayer Platform](https://platform.dreamlayer.io/?utm_source=github&utm_medium=agent_distribution&utm_campaign=agent_launch). + +For the CLI, add the server to `~/.cline/data/settings/cline_mcp_settings.json`. Run `cline config mcp` to print the exact path your build uses and to list what is configured. + +```json +{"mcpServers":{"dreamlayer":{"command":"npx","args":["-y","@dreamlayer/mcp@0.4.0-beta.4"],"env":{"DREAMLAYER_API_KEY":"YOUR_KEY"},"disabled":false,"autoApprove":[],"timeout":120}}} +``` + +In the IDE extension, open the MCP Servers icon, then Configure, then Configure MCP Servers, and add the same entry. + +That file sits outside your repository. Keep the key out of the project, out of prompts and out of commits, and rotate it on the platform if it reaches any of them. Leave `autoApprove` empty so each paid call is reviewed; only the read-only tools below are safe to approve in advance. + +The `timeout` value matters on a machine that has never run the package: `npx -y` downloads it first, measured at 13 to 50 seconds across two clean profiles. Install it ahead of time with `npm i -g @dreamlayer/mcp@0.4.0-beta.4` to remove the wait. + +## The seven tools + +| Tool | Use it for | Credits | +| --- | --- | --- | +| dreamlayer_capabilities | Supported operations, input limits, live sprite pricing | none | +| dreamlayer_balance | Available credits for this key | none | +| dreamlayer_upload_image | Register a local reference, returns input_asset_id | none | +| dreamlayer_generate | The paid call: text_to_image, image_to_image, background_remove, upscale, sprite_sheet | charged | +| dreamlayer_execution | Canonical status of an execution you already started | none | +| dreamlayer_events | Resume a dropped stream from last_event_id | none | +| dreamlayer_download | Write a finished image to an absolute new local path | none | + +## Generate, poll or resume, download + +1. Read capabilities and balance. Both are free and both must succeed before any paid call. +2. For an edit, cutout, upscale or sprite, call upload_image once and keep the `input_asset_id`. +3. Save a unique `idempotency_key` and the whole request locally, then call generate with an explicit operation. It returns `execution_id`, `status`, `asset`, `question` and `last_event_id`. +4. While the job runs, call execution or events with that `execution_id`. Never call generate again to check on a job; that starts a second paid run. +5. A `needs_input` result is a question, not a failure. Answer it with the `conversation_id`. +6. Call download with the `execution_id` and a new absolute path, then open the file. A returned URL is not proof that a local file exists. + +Plan mode is a good place to agree the operation, the number of outputs and the budget before Act mode spends anything. + +## Credits and recovery + +A completed standard image operation currently costs one credit, and a multi-step edit and cutout counts as two operations. Sprite jobs are quoted live by frame count, currently 5.8 credits for 7 frames and 9.9 for 12. Take the number from capabilities, not from this page, and stay inside the budget the user already approved. A new API account starts at zero credits, and neither Cline nor these skills may buy more. + +After a timeout, rate limit or crash, recover by `execution_id` with execution, events or download. Stop and report on insufficient credits, an invalid request, an unsupported operation or an unresolved contract error. Do not loop paid retries. + +## Limits + +Sprite output is beta. A PNG does not establish vector structure, pixel-perfect art, trademark clearance, print compliance or exact product fidelity, so inspect each file before shipping it. The `unity-sprite-import` workflow is validated only by static checks and its Python test; a real Unity editor import remains unconfirmed, and the steps to confirm it are in the [Unity editor test plan](unity-editor-test-plan.md). The `godot-sprite-import` workflow has been run against Godot 4. + +## Validated + +Cline CLI 3.0.65 reported all nine workflows under `Enabled skills:` from `.cline/skills/` in a clean profile, with the generated README ignored, `cline skill add` installed the same nine to `.agents/skills/` and listed them as Cline skills, and `cline config mcp` listed the pinned stdio server after the settings file was written. No generation was run and no credits were spent. diff --git a/docs/copilot-setup.md b/docs/copilot-setup.md new file mode 100644 index 0000000..40d08da --- /dev/null +++ b/docs/copilot-setup.md @@ -0,0 +1,86 @@ +# GitHub Copilot setup + +Use DreamLayer when a task needs actual image files: an original image, a raster logo or app-icon concept, product and marketing visuals, print-on-demand artwork, a background removal or upscale, a bounded batch of photo edits, or a sprite-sheet animation from one reference. Copilot keeps writing the code; DreamLayer produces the assets the code ships. + +## Install the skills + +This repository already carries the nine workflows in `.github/skills/`, which Copilot reads as project skills. Copilot also reads `.agents/skills/` and `.claude/skills/`, and this repository generates `.agents/skills/` from the same source. + +```sh +git clone https://github.com/TheDesignFounder/dreamlayer-agent-plugin.git +cp -R dreamlayer-agent-plugin/.github/skills/. your-project/.github/skills/ +``` + +For skills that follow you between projects, copy the same folders into `~/.copilot/skills/` or `~/.agents/skills/`, or run `copilot skill add `. + +Confirm discovery from your project root: + +```sh +copilot skill list +``` + +Nine DreamLayer entries appear under `Project skills:`. `copilot skill disable ` hides one you do not want. + +## The nine workflows + +| Workflow | Makes | +| --- | --- | +| generate-or-edit-image | An original image, an edit, a cutout or an upscale | +| logo-and-app-icon-concepts | Raster logo and app-icon directions, not vector masters | +| product-image-generation | Ecommerce backgrounds and lifestyle concepts from a product photo | +| print-on-demand-artwork | Shirt, sticker and poster artwork with transparent PNG preparation | +| marketing-image-variations | Ad, banner and social variations from a brief or reference | +| batch-photo-editing | One edit, cutout or upscale applied across a list of files, with a budget | +| sprite-sheet-from-reference | A sprite-sheet ZIP of transparent frames, atlas and preview | +| godot-sprite-import | A runnable Godot 4 demo from a downloaded sprite ZIP | +| unity-sprite-import | Unity 2D import files and a frame player from a downloaded sprite ZIP | + +## Connect the tools + +Requires Node.js 22.12 or later and a key from [DreamLayer Platform](https://platform.dreamlayer.io/?utm_source=github&utm_medium=agent_distribution&utm_campaign=agent_launch). + +```sh +copilot mcp add dreamlayer --env DREAMLAYER_API_KEY=YOUR_KEY -- npx -y @dreamlayer/mcp@0.4.0-beta.4 +copilot mcp list +``` + +The entry lands in `~/.copilot/mcp-config.json`, outside your repository, and Copilot masks the value when it prints the server. A workspace-level `.mcp.json` or `.github/mcp.json` works too, but those files are committed, so keep the key out of them. In VS Code use `.vscode/mcp.json` with a `promptString` input, as shown in [client setup](client-setup.md). + +Never paste the key into a prompt, a skill file or a commit. Rotate it on the platform if it reaches any of those. + +The first run downloads the package. On a machine that has never run it, allow 13 to 50 seconds before deciding the server failed, or install it ahead of time with `npm i -g @dreamlayer/mcp@0.4.0-beta.4`. + +## The seven tools + +| Tool | Use it for | Credits | +| --- | --- | --- | +| dreamlayer_capabilities | Supported operations, input limits, live sprite pricing | none | +| dreamlayer_balance | Available credits for this key | none | +| dreamlayer_upload_image | Register a local reference, returns input_asset_id | none | +| dreamlayer_generate | The paid call: text_to_image, image_to_image, background_remove, upscale, sprite_sheet | charged | +| dreamlayer_execution | Canonical status of an execution you already started | none | +| dreamlayer_events | Resume a dropped stream from last_event_id | none | +| dreamlayer_download | Write a finished image to an absolute new local path | none | + +## Generate, poll or resume, download + +1. Read capabilities and balance. Both are free and both must succeed before any paid call. +2. For an edit, cutout, upscale or sprite, call upload_image once and keep the `input_asset_id`. +3. Save a unique `idempotency_key` and the whole request locally, then call generate with an explicit operation. It returns `execution_id`, `status`, `asset`, `question` and `last_event_id`. +4. While the job runs, call execution or events with that `execution_id`. Never call generate again to check on a job; that starts a second paid run. +5. A `needs_input` result is a question, not a failure. Answer it with the `conversation_id`. +6. Call download with the `execution_id` and a new absolute path, then open the file. A returned URL is not proof that a local file exists. + +## Credits and recovery + +A completed standard image operation currently costs one credit, and a multi-step edit and cutout counts as two operations. Sprite jobs are quoted live by frame count, currently 5.8 credits for 7 frames and 9.9 for 12. Take the number from capabilities, not from this page, and stay inside the budget the user already approved. A new API account starts at zero credits, and neither Copilot nor these skills may buy more. + +After a timeout, rate limit or crash, recover by `execution_id` with execution, events or download. Stop and report on insufficient credits, an invalid request, an unsupported operation or an unresolved contract error. Do not loop paid retries. + +## Limits + +Sprite output is beta. A PNG does not establish vector structure, pixel-perfect art, trademark clearance, print compliance or exact product fidelity, so inspect each file before shipping it. The `unity-sprite-import` workflow is validated only by static checks and its Python test; a real Unity editor import remains unconfirmed, and the steps to confirm it are in the [Unity editor test plan](unity-editor-test-plan.md). The `godot-sprite-import` workflow has been run against Godot 4. + +## Validated + +GitHub Copilot CLI 1.0.88 listed all nine workflows from `.github/skills/` in a clean profile with no sign-in, and `copilot mcp add` plus `copilot mcp list` registered the pinned server with the key masked in the output. No generation was run and no credits were spent. diff --git a/scripts/build_plugins.py b/scripts/build_plugins.py deleted file mode 100644 index b3e42d8..0000000 --- a/scripts/build_plugins.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Regenerate self-contained plugins from the canonical skills.""" -import shutil -from pathlib import Path -ROOT=Path(__file__).resolve().parents[1] -source=ROOT/'.agents/skills' -for skill in source.iterdir(): - if skill.is_dir(): - shutil.copyfile(ROOT/'docs/workflow-contract.md',skill/'references/workflow-contract.md') - if skill.name in ('godot-sprite-import','unity-sprite-import'): - (skill/'scripts').mkdir(exist_ok=True) - shutil.copyfile(ROOT/'scripts/import_sprite.py',skill/'scripts/import_sprite.py') - shutil.copytree(skill,ROOT/'plugins/dreamlayer/skills'/skill.name,dirs_exist_ok=True) -print(f'Built {len(list(source.iterdir()))} skills') diff --git a/scripts/sync_skills.py b/scripts/sync_skills.py new file mode 100644 index 0000000..a0266dd --- /dev/null +++ b/scripts/sync_skills.py @@ -0,0 +1,113 @@ +"""Generate every host skill directory from the one canonical source. + +Canonical: plugins/dreamlayer/skills. Every other location is a byte-for-byte +copy produced here, so a workflow can never differ between hosts. Run without +arguments to regenerate, with --check to fail on drift (CI and validate.py). +""" +import sys +from pathlib import Path + +CANONICAL = 'plugins/dreamlayer/skills' +MIRRORS = { + '.agents/skills': 'Cursor, OpenCode, GitHub Copilot and the npx skills installer', + '.github/skills': 'GitHub Copilot in the CLI, VS Code and the coding agent', + '.cline/skills': 'Cline', +} +VENDORED = {'references/workflow-contract.md': 'docs/workflow-contract.md'} +ENGINE_SCRIPTS = {'godot-sprite-import', 'unity-sprite-import'} +IGNORED = {'.DS_Store', '__pycache__'} + + +def readme(purpose): + return ( + '# Generated skills\n\n' + 'Do not edit these files. They are copied byte for byte from\n' + f'`{CANONICAL}/`, the canonical source, so that {purpose} load the same\n' + 'workflows as every other host.\n\n' + 'Change a workflow in the canonical source, then regenerate:\n\n' + '```sh\npython3 scripts/sync_skills.py\n```\n\n' + '`python3 scripts/sync_skills.py --check` fails when any copy has drifted, and\n' + '`python3 scripts/validate.py` runs that check.\n' + ) + + +def tracked(root): + """Files under root, ignoring editor and interpreter leftovers.""" + return sorted( + path for path in root.rglob('*') + if path.is_file() and not IGNORED & set(path.relative_to(root).parts) + ) + + +def vendor(root, check): + """Refresh the shared files each skill carries so it travels self-contained.""" + problems = [] + for skill in sorted(path for path in (root / CANONICAL).iterdir() if path.is_dir()): + wanted = dict(VENDORED) + if skill.name in ENGINE_SCRIPTS: + wanted['scripts/import_sprite.py'] = 'scripts/import_sprite.py' + for relative, source in wanted.items(): + data = (root / source).read_bytes() + destination = skill / relative + if destination.exists() and destination.read_bytes() == data: + continue + problems.append(f'{destination.relative_to(root)}: stale copy of {source}') + if not check: + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(data) + return problems + + +def sync(root=None, check=False): + """Return the drift found; write the mirrors too unless check is set.""" + root = Path(root or Path(__file__).resolve().parents[1]) + problems = vendor(root, check) + canonical = { + path.relative_to(root / CANONICAL): path.read_bytes() + for path in tracked(root / CANONICAL) + } + for mirror, purpose in MIRRORS.items(): + target = root / mirror + wanted = dict(canonical) + wanted[Path('README.md')] = readme(purpose).encode() + for relative, data in sorted(wanted.items()): + destination = target / relative + if destination.exists() and destination.read_bytes() == data: + continue + problems.append( + f'{mirror}/{relative}: ' + + ('missing' if not destination.exists() else 'differs from the canonical source') + ) + if not check: + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_bytes(data) + present = {path.relative_to(target) for path in tracked(target)} if target.exists() else set() + for relative in sorted(present - set(wanted)): + problems.append(f'{mirror}/{relative}: not in the canonical source') + if not check: + (target / relative).unlink() + if not check and target.exists(): + for path in sorted(target.rglob('*'), reverse=True): + if path.is_dir() and not any(path.iterdir()): + path.rmdir() + return problems + + +def main(argv): + check = '--check' in argv + problems = sync(check=check) + if problems and check: + print('DRIFT:') + for problem in problems: + print(f' {problem}') + print(f'{len(problems)} problem(s). Run: python3 scripts/sync_skills.py') + return 1 + root = Path(__file__).resolve().parents[1] + skills = len([path for path in (root / CANONICAL).iterdir() if path.is_dir()]) + state = f'rewrote {len(problems)} file(s)' if problems else 'already matched' + print(f'{skills} skills, {state}: {", ".join(MIRRORS)} (canonical {CANONICAL})') + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/scripts/validate.py b/scripts/validate.py index 23b78f0..bfc8fb2 100644 --- a/scripts/validate.py +++ b/scripts/validate.py @@ -1,31 +1,37 @@ """Check portable packaging and exact parity.""" -import json,re +import importlib.util,json,re from pathlib import Path ROOT=Path(__file__).resolve().parents[1] -count=0 -for folder in (ROOT/'.agents/skills').iterdir(): +spec=importlib.util.spec_from_file_location('sync_skills',ROOT/'scripts/sync_skills.py') +sync_skills=importlib.util.module_from_spec(spec);spec.loader.exec_module(sync_skills) +drift=sync_skills.sync(check=True) +assert not drift,'Host copies have drifted from the canonical source:\n '+'\n '.join(drift) +CANONICAL=ROOT/sync_skills.CANONICAL +skills=sorted(folder for folder in CANONICAL.iterdir() if folder.is_dir()) +for folder in skills: text=(folder/'SKILL.md').read_text() assert text.startswith('---\n') and f'name: {folder.name}\n' in text assert re.search(r'^description: .+',text,re.M) - for file in folder.rglob('*'): - if file.is_file(): - copy=ROOT/'plugins/dreamlayer/skills'/file.relative_to(ROOT/'.agents/skills') - assert copy.read_bytes()==file.read_bytes(),f'Drift: {file}' + assert re.fullmatch(r'[a-z0-9]+(-[a-z0-9]+)*',folder.name),folder.name for link in re.findall(r'\]\(([^)]+)\)',text): if not link.startswith('http'): assert (folder/link).exists(),link - count+=1 for path in ROOT.rglob('*.json'): if 'node_modules' not in path.parts: json.loads(path.read_text()) config=json.loads((ROOT/'plugins/dreamlayer/.mcp.json').read_text()) assert config['mcpServers']['dreamlayer']['args']==['-y','@dreamlayer/mcp@0.4.0-beta.4'] -assert count==9 +assert len(skills)==9 clawhub=ROOT/'clawhub/dreamlayer-image-workflows/SKILL.md' text=clawhub.read_text() assert text.startswith('---\n') and 'name: dreamlayer-image-workflows\n' in text assert '@dreamlayer/mcp@0.4.0-beta.4' in text and '0.4.0-beta.3' not in text assert 'dreamlayer_cancel' not in text -for folder in (ROOT/'.agents/skills').iterdir(): +for folder in skills: assert folder.name in text, f'ClawHub entry point does not route to {folder.name}' -print(f'PASS: {count} skills, valid JSON/links, pinned MCP, identical copies, ClawHub entry point routes to all of them') - +for doc in ('docs/copilot-setup.md','docs/cline-setup.md'): + body=(ROOT/doc).read_text() + for folder in skills: + assert folder.name in body, f'{doc} does not list {folder.name}' + assert '@dreamlayer/mcp@0.4.0-beta.4' in body and 'Unity' in body +print(f'PASS: {len(skills)} skills, valid JSON/links, pinned MCP, host copies identical to ' + f'{sync_skills.CANONICAL} ({", ".join(sync_skills.MIRRORS)}), ClawHub and host docs route to all of them') diff --git a/tests/test_sync_skills.py b/tests/test_sync_skills.py new file mode 100644 index 0000000..ce7eec5 --- /dev/null +++ b/tests/test_sync_skills.py @@ -0,0 +1,86 @@ +import importlib.util, shutil, tempfile, unittest +from pathlib import Path +ROOT=Path(__file__).resolve().parents[1] +spec=importlib.util.spec_from_file_location('sync_skills',ROOT/'scripts/sync_skills.py') +sync_skills=importlib.util.module_from_spec(spec);spec.loader.exec_module(sync_skills) + +def build(root): + """A miniature repository: one plain skill and one that carries the import script.""" + (root/'docs').mkdir(parents=True);(root/'docs/workflow-contract.md').write_text('contract v1\n') + (root/'scripts').mkdir();(root/'scripts/import_sprite.py').write_text('# importer v1\n') + for name in ('demo-skill','godot-sprite-import'): + folder=root/sync_skills.CANONICAL/name;folder.mkdir(parents=True) + (folder/'SKILL.md').write_text(f'---\nname: {name}\ndescription: Demo.\n---\n\nBody.\n') + return root + +class SyncTests(unittest.TestCase): + def setUp(self): + self.tmp=tempfile.TemporaryDirectory();self.addCleanup(self.tmp.cleanup) + self.root=build(Path(self.tmp.name)) + + def mirrors(self): + return [self.root/mirror for mirror in sync_skills.MIRRORS] + + def test_generates_every_host_path_then_reports_clean(self): + self.assertTrue(sync_skills.sync(self.root,check=True)) + for mirror in self.mirrors(): self.assertFalse(mirror.exists()) + sync_skills.sync(self.root) + self.assertEqual(sync_skills.sync(self.root,check=True),[]) + for mirror in self.mirrors(): + self.assertEqual((mirror/'demo-skill/SKILL.md').read_text(), + (self.root/sync_skills.CANONICAL/'demo-skill/SKILL.md').read_text()) + self.assertEqual((mirror/'godot-sprite-import/scripts/import_sprite.py').read_text(),'# importer v1\n') + self.assertEqual((mirror/'demo-skill/references/workflow-contract.md').read_text(),'contract v1\n') + self.assertFalse((mirror/'demo-skill/scripts/import_sprite.py').exists()) + self.assertIn(sync_skills.CANONICAL,(mirror/'README.md').read_text()) + + def test_check_finds_edited_copy_and_rewrite_restores_it(self): + sync_skills.sync(self.root) + edited=self.root/'.cline/skills/demo-skill/SKILL.md' + edited.write_text('---\nname: demo-skill\ndescription: Forked.\n---\n') + problems=sync_skills.sync(self.root,check=True) + self.assertEqual(len(problems),1) + self.assertIn('.cline/skills/demo-skill/SKILL.md',problems[0]) + self.assertIn('differs',problems[0]) + self.assertEqual(edited.read_text(),'---\nname: demo-skill\ndescription: Forked.\n---\n') + sync_skills.sync(self.root) + self.assertIn('Body.',edited.read_text()) + self.assertEqual(sync_skills.sync(self.root,check=True),[]) + + def test_check_finds_extra_file_and_rewrite_removes_it(self): + sync_skills.sync(self.root) + stray=self.root/'.github/skills/demo-skill/EXTRA.md' + stray.write_text('not canonical\n') + problems=sync_skills.sync(self.root,check=True) + self.assertEqual([p for p in problems if 'EXTRA.md' in p and 'not in the canonical source' in p],problems) + sync_skills.sync(self.root) + self.assertFalse(stray.exists()) + + def test_vendored_file_change_reaches_every_copy(self): + sync_skills.sync(self.root) + (self.root/'docs/workflow-contract.md').write_text('contract v2\n') + self.assertTrue(any('stale copy' in p for p in sync_skills.sync(self.root,check=True))) + sync_skills.sync(self.root) + for mirror in self.mirrors(): + self.assertEqual((mirror/'demo-skill/references/workflow-contract.md').read_text(),'contract v2\n') + self.assertEqual(sync_skills.sync(self.root,check=True),[]) + + def test_editor_leftovers_are_not_drift(self): + sync_skills.sync(self.root) + (self.root/'.agents/skills/demo-skill/.DS_Store').write_bytes(b'\x00') + cache=self.root/'.agents/skills/demo-skill/__pycache__';cache.mkdir() + (cache/'x.pyc').write_bytes(b'\x00') + self.assertEqual(sync_skills.sync(self.root,check=True),[]) + + def test_canonical_source_is_never_rewritten_from_a_mirror(self): + sync_skills.sync(self.root) + canonical=self.root/sync_skills.CANONICAL/'demo-skill/SKILL.md' + before=canonical.read_bytes() + shutil.rmtree(self.root/'.cline/skills/demo-skill') + (self.root/'.github/skills/demo-skill/SKILL.md').write_text('forked\n') + sync_skills.sync(self.root) + self.assertEqual(canonical.read_bytes(),before) + self.assertEqual((self.root/'.github/skills/demo-skill/SKILL.md').read_bytes(),before) + self.assertTrue((self.root/'.cline/skills/demo-skill/SKILL.md').exists()) + +if __name__=='__main__': unittest.main() From bc5f3c7df68dc1b307d16b386f04d0d6b22c66aa Mon Sep 17 00:00:00 2001 From: TheDesignFounder Date: Thu, 24 Sep 2026 23:48:35 -0700 Subject: [PATCH 2/2] Record the host discovery results Copilot, Cline and OpenCode each listed the nine workflows from the path they document, in a clean profile with no account and no generation. Co-authored-by: Claude Opus 5 (1M context) --- docs/validation.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/validation.md b/docs/validation.md index 7f06f11..8a9f785 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -30,6 +30,21 @@ These are review scenarios and expected behaviors, not a claim of model-based ev | Ask for a 20-image batch with two credits | Do not silently spend beyond cap; clarify/reduce scope only with authorization | | Ask to overwrite an existing paid output or retry after uncertain submission | Preserve existing output/job; recover by ID instead of replacing generation | +## Host discovery, September 25, 2026 + +Each host ran in a clean isolated profile on Node 24.21.0, with no account and no generation. No credits were spent. + +| Host | Command | Result | +| --- | --- | --- | +| GitHub Copilot CLI 1.0.88 | `copilot skill list` | Nine project skills from `.github/skills/` | +| GitHub Copilot CLI 1.0.88 | `copilot mcp add` and `copilot mcp list` | Pinned stdio server registered, key printed masked | +| Cline CLI 3.0.65 | `cline config skills` | Nine enabled skills from `.cline/skills/` | +| Cline CLI 3.0.65 | `cline skill add ... -a cline` | Installed the same nine to `.agents/skills/`, listed as Cline skills | +| Cline CLI 3.0.65 | `cline config mcp` | Pinned stdio server listed from the CLI settings file | +| OpenCode 1.18.32 | `opencode debug skill` | Nine skills from `.agents/skills/`, alongside its own built-in | + +Every host ignored the generated `README.md` in the skills root. Cursor documents the same `.agents/skills/` project path but was not run here, so it stays a documented path rather than a tested one. Cline's own runtime skill loading inside VS Code was not exercised; the CLI reading `.cline/skills/` is the evidence. + - Public GitHub marketplace: installed successfully with Codex 0.155.1 `plugin add dreamlayer@dreamlayer`. - `npx skills add TheDesignFounder/dreamlayer-agent-plugin --list`: discovered all nine canonical skills. - MCPB manifest validation passed; packaged published beta.3 and dependencies without credentials.