@sogni-ai/sogni-creative-agent-skill 3.3.5 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -70,14 +70,23 @@ With this skill, an agent can:
70
70
  ## Quick Start
71
71
 
72
72
  1. Get a Sogni API key from [dashboard.sogni.ai](https://dashboard.sogni.ai) (open the account menu) and save it — see [Setup](#setup-sogni-api-key).
73
- 2. Install the CLI:
73
+ 2. Install (one command):
74
+
75
+ ```bash
76
+ npx setup-sogni-agent-skill
77
+ ```
78
+
79
+ This auto-detects Claude Code, Codex CLI, and Hermes; installs the CLI globally;
80
+ registers the skill into each detected runtime; and prompts for your API key.
81
+
82
+ Prefer to do it manually? Install the CLI directly:
74
83
 
75
84
  ```bash
76
85
  npm install -g @sogni-ai/sogni-creative-agent-skill@latest
77
86
  sogni-agent --version
78
87
  ```
79
88
 
80
- 3. Point your agent runtime at this repository's [`SKILL.md`](./SKILL.md).
89
+ Then point your agent runtime at this repository's [`SKILL.md`](./SKILL.md).
81
90
 
82
91
  Then ask your agent to do something:
83
92
 
@@ -133,6 +142,8 @@ openclaw plugins install sogni-creative-agent-skill
133
142
 
134
143
  The installed plugin loads its behavior from [`SKILL.md`](./SKILL.md) via [`openclaw.plugin.json`](./openclaw.plugin.json).
135
144
 
145
+ > **API key under OpenClaw:** the plugin config holds non-secret defaults only (models, timeouts, paths) — it does **not** carry your API key. Provide `SOGNI_API_KEY` via the environment the OpenClaw gateway passes to the CLI, or save it to `~/.config/sogni/credentials` (`SOGNI_API_KEY=<your-key>`). This keeps your key out of plugin config files.
146
+
136
147
  For a local checkout that you want to update continuously, link the minimal OpenClaw surface (`.openclaw-link/`) — not the repository root, which contains development tests that OpenClaw correctly blocks during plugin safety scanning:
137
148
 
138
149
  ```bash
@@ -451,7 +462,7 @@ LTX-2.3 prompt: "A medium cinematic shot frames a woman in her 30s standing in a
451
462
 
452
463
  ## Photobooth (Face Transfer)
453
464
 
454
- Generate stylized portraits from a face photo using InstantID ControlNet:
465
+ Generate new stylized portraits from a face photo using InstantID ControlNet:
455
466
 
456
467
  ```bash
457
468
  sogni-agent --photobooth --ref face.jpg "80s fashion portrait"
@@ -460,6 +471,8 @@ sogni-agent --photobooth --ref face.jpg -n 4 "LinkedIn professional headshot"
460
471
 
461
472
  Uses SDXL Turbo (`coreml-sogniXLturbo_alpha1_ad`) at 1024×1024 by default. The face image is passed via `--ref` and styled by the prompt. Cannot be combined with `--video` or `-c` / `--context`.
462
473
 
474
+ `--photobooth` is face-reference generation, not full-image editing. If the request is "same image, different style" — for example an anime version that must keep the same face, pose, clothing, background, framing, and composition — use Qwen image editing with `-c/--context` instead.
475
+
463
476
  Multi-angle mode (`--multi-angle` / `--angles-360`) auto-builds the `<sks>` prompt and applies the `multiple_angles` LoRA. `--angles-360-video` generates i2v clips between consecutive angles (including last → first) and concatenates them with `ffmpeg` into a seamless loop.
464
477
 
465
478
  `--balance` / `--balances` does not require a prompt and prints current `SPARK` and `SOGNI` balances before exiting.
@@ -577,7 +590,7 @@ Use `--token-type auto` to retry native Sogni models with SOGNI tokens when SPAR
577
590
  sogni-agent --token-type auto "a dragon eating tacos"
578
591
  ```
579
592
 
580
- Tries SPARK first (free daily tokens), then falls back to SOGNI if the balance is too low. Vendor models such as Seedance and GPT Image 2 require Premium Spark eligibility and never use SOGNI fallback.
593
+ Tries SPARK first, then falls back to SOGNI if the balance is too low. Vendor models such as Seedance and GPT Image 2 require Premium Spark eligibility and never use SOGNI fallback. If usable balance is still insufficient, buy Spark Packs at https://docs.sogni.ai/pricing/#spark-packs.
581
594
 
582
595
  ---
583
596
 
@@ -632,6 +645,15 @@ Reusable workflow rules should come from the shared Sogni runtime before they ar
632
645
 
633
646
  Public-skill regex should stay limited to CLI argument/fact extraction such as file paths, URLs, extensions, dimensions, durations, and explicit positions. Hosted-style decisions such as latest-video continuation, uploaded-video modification, image-selection waits, stitch-after-batch state, and repair/control routing belong upstream in typed planner/runtime fields before they are synced here.
634
647
 
648
+ ### Execution boundaries: local CLI vs. hosted surfaces
649
+
650
+ `sogni-agent.mjs` is a **local command-line tool** (`#!/usr/bin/env node`, the package `bin`). It is the only place that may assume a local filesystem and a local `ffmpeg`/`ffprobe` binary. Flags like `--concat-videos`, `--remix-audio`, `--extract-first-frame`, `--extract-last-frame`, and `--angles-360-video` shell out to ffmpeg behind `ensureFfmpegAvailable()` and run only when those flags are passed.
651
+
652
+ Hosted surfaces — including the chat.sogni.ai web app — do **not** run `sogni-agent.mjs`. They consume `@sogni-ai/sogni-intelligence-client` and the hosted `/v1/chat/completions` and `/v1/creative-agent/workflows` APIs, where there is no local ffmpeg and no local filesystem. Therefore:
653
+
654
+ - Keep ffmpeg- and filesystem-dependent helpers (frame extraction, concat, audio remux, media listing) **local to `sogni-agent.mjs`**. Do not move them into the shared runtime or `@sogni-ai/sogni-intelligence-client`, and never make hosted code paths depend on a local binary.
655
+ - Server-side equivalents of these capabilities (e.g. `stitch_video`, `overlay_video`, `extend_video`) live in the hosted creative-agent tool surface and belong upstream, not in the CLI.
656
+
635
657
  Issues and feature requests: [github.com/Sogni-AI/sogni-creative-agent-skill/issues](https://github.com/Sogni-AI/sogni-creative-agent-skill/issues).
636
658
 
637
659
  ---
package/SKILL.md CHANGED
@@ -2,7 +2,7 @@
2
2
  name: sogni-creative-agent-skill
3
3
  description: "Sogni Creative Agent Skill: agent skill and CLI for image, video, and music generation using Sogni AI's decentralized GPU network. Supports personas (named people with saved reference photos and voice clips), persistent memories, custom personality, style transfer, angle synthesis, Seedance/LTX/WAN video, music/lyrics, hosted chat, durable workflows, replay records, and multi-step creative workflows. Ask the agent to \"draw\", \"generate\", \"create an image\", \"make a video/animate\", \"make music\", \"apply a style\", or \"generate me as a superhero\"."
4
4
  metadata:
5
- version: "3.3.5"
5
+ version: "3.4.0"
6
6
  homepage: https://sogni.ai
7
7
  clawdbot:
8
8
  emoji: "🎨"
@@ -72,6 +72,10 @@ npm --prefix "$DEST" install
72
72
 
73
73
  If that checkout does not exist, prefer the npm-based local skill install below, or ask before cloning.
74
74
 
75
+ ## Uninstall Request Policy
76
+
77
+ When a user asks to uninstall, run `npx setup-sogni-agent-skill --uninstall --remove-cli --purge`. This removes the skill files, the global CLI, and the user's data in `~/.config/sogni/` after backing it up to `~/.config/sogni.backup-<timestamp>.tar.gz`. Always tell the user the backup path and that it contains their API key. To keep their data, omit `--purge`.
78
+
75
79
  ## Setup
76
80
 
77
81
  1. **Get your Sogni API key** by logging into https://dashboard.sogni.ai and opening the account menu.
@@ -463,9 +467,19 @@ positions.
463
467
  | `--strict-size` | Do not auto-adjust i2v video size for reference resizing constraints | false |
464
468
  | `-q, --quiet` | No progress output | false |
465
469
  | `--extract-last-frame <video> <image>` | Extract last frame from video (safe ffmpeg wrapper) | - |
466
- | `--concat-videos <out> <clips...>` | Concatenate video clips (safe ffmpeg wrapper) | - |
470
+ | `--extract-first-frame <video> <image>` | Extract first frame from video (safe ffmpeg wrapper) | - |
471
+ | `--concat-videos <out> <clips...>` | Concatenate video clips; normalizes fps/size and fills silent audio so mismatched clips stitch cleanly (safe ffmpeg wrapper) | - |
472
+ | `--concat-fps <n>` | Override target fps for `--concat-videos` | highest clip fps |
467
473
  | `--concat-audio <path>` | Optional audio track to mux over `--concat-videos` output | - |
468
474
  | `--concat-audio-start <sec>` | Start offset into `--concat-audio` | - |
475
+ | `--remix-audio <in> <out>` | Rebuild a video's audio (loop/fade/mix) without re-encoding video (safe ffmpeg wrapper) | - |
476
+ | `--bed-audio <path>` | Audio bed for `--remix-audio` (path or video; defaults to input's own audio) | - |
477
+ | `--audio-loop` | Loop the bed to cover the full video duration (`--remix-audio`) | false |
478
+ | `--audio-fade-in <sec>` | Fade the bed in over `<sec>` (`--remix-audio`) | - |
479
+ | `--audio-fade-out <sec>` | Fade the bed out over `<sec>` at the tail (`--remix-audio`) | - |
480
+ | `--mix-audio <path>` | Overlay one extra audio track, mixed with the bed (`--remix-audio`) | - |
481
+ | `--mix-at <sec>` | Start offset for `--mix-audio` | 0 |
482
+ | `--mix-gain <db>` | Gain in dB applied to `--mix-audio` | 0 |
469
483
  | `--list-media [type]` | List recent inbound media (images\|audio\|all) | images |
470
484
  | `--api-chat` | Call OpenAI-compatible `/v1/chat/completions`; CLI default sends the hosted `creative-agent` tool surface | - |
471
485
  | `--durable-chat` | Start and stream a durable `/v1/chat/runs` record through SDK transport; requires `SOGNI_SKILL_USE_SDK_TRANSPORT=1` | - |
@@ -665,9 +679,17 @@ sogni-agent --last-image "make it more vibrant"
665
679
 
666
680
  When context images are provided without `-m`, defaults to `qwen_image_edit_2511_fp8_lightning`. Select `-m gpt-image-2` for GPT Image 2's higher reference-image limit and OpenAI-backed image editing.
667
681
 
682
+ Use context-image editing for source-preserving edits. If the user says "use this image as the base", "keep everything the same", "only change the style", "anime version of this image", or asks to preserve pose, clothing, background, framing, or composition, use `-c/--context` with a Qwen image edit model instead of `--photobooth`. For stronger preservation than the lightning default, prefer:
683
+
684
+ ```bash
685
+ sogni-agent -c photo.jpg -m qwen_image_edit_2511_fp8 "turn this into anime style; keep the same face, pose, clothing, background, framing, and composition"
686
+ ```
687
+
668
688
  ## Photobooth (Face Transfer)
669
689
 
670
- Generate stylized portraits from a face photo using InstantID ControlNet. When a user mentions "photobooth", wants a stylized portrait of themselves, or asks to transfer their face into a style, use `--photobooth` with `--ref` pointing to their face image.
690
+ Generate new stylized portraits from a face photo using InstantID ControlNet. Use `--photobooth` with `--ref` when the user explicitly asks for photobooth/face-transfer mode, wants a new portrait or headshot based on their face, or asks to place their face identity into a different portrait concept.
691
+
692
+ Do not use `--photobooth` for full-image style edits where the original photo must stay intact. `--photobooth` treats the input as a face reference, not as a base image, so it can change pose, clothing, background, framing, and composition. For "same image, different style" requests, route to Qwen context editing with `-c/--context`.
671
693
 
672
694
  ```bash
673
695
  # Basic photobooth
@@ -744,7 +766,7 @@ When a user requests a "360 video", follow this workflow:
744
766
 
745
767
  ### Transition Video Rule
746
768
 
747
- For **any transition video work**, always use the **Sogni skill/plugin** (not raw ffmpeg or other shell commands). Use the built-in `--extract-last-frame`, `--concat-videos`, and `--looping` flags for video manipulation.
769
+ For **any transition video work**, always use the **Sogni skill/plugin** (not raw ffmpeg or other shell commands). Use the built-in `--extract-last-frame`, `--extract-first-frame`, `--concat-videos`, `--remix-audio`, and `--looping` flags for video and audio manipulation.
748
770
 
749
771
  ### Insufficient Funds Handling
750
772
 
@@ -752,7 +774,9 @@ Use `--token-type auto` to automatically retry native Sogni models with SOGNI to
752
774
 
753
775
  When you see **"Debit Error: Insufficient funds"** even with auto-fallback, reply:
754
776
 
755
- "Insufficient funds. Claim 50 free daily Spark points at https://app.sogni.ai/"
777
+ "Insufficient funds. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
778
+
779
+ Do not collect payment details, quote a custom price, or simulate a purchase in the terminal.
756
780
 
757
781
  ## Video Generation
758
782
 
@@ -877,10 +901,11 @@ sogni-agent --json --list-media images
877
901
 
878
902
  **Do NOT use `ls`, `cp`, or other shell commands to browse user files.** Always use `--list-media` to find inbound media.
879
903
 
880
- ## IMPORTANT KEYWORD RULE
904
+ ## Photobooth Routing Rule
881
905
 
882
- - If the user message includes the word "photobooth" (case-insensitive), always use `--photobooth` mode with `--ref` set to the user-provided face image.
883
- - Prioritize this rule over generic image-edit flows (`-c`) for that request.
906
+ - If the user explicitly asks to use "photobooth", "photobooth path", or "face transfer", use `--photobooth` with `--ref` set to the user-provided face image.
907
+ - If the same request also requires preserving the whole source image (same pose, clothes, background, framing, composition, or "keep everything the same"), explain that photobooth is face-reference generation and prefer Qwen context editing unless the user insists on photobooth.
908
+ - Do not route to `--photobooth` merely because the user asks to preserve a face in a style edit. Face-preserving full-image edits should use `-c/--context` with Qwen image edit.
884
909
 
885
910
  ## LTX-2.3 Prompt Rule
886
911
 
@@ -1017,7 +1042,7 @@ If clips need different source images, end frames, durations, audio windows, or
1017
1042
 
1018
1043
  ### Token Auto-Fallback
1019
1044
 
1020
- Use `--token-type auto` when the user's SPARK balance might be low. It tries SPARK first (free daily tokens) and automatically retries with SOGNI if insufficient.
1045
+ Use `--token-type auto` when the user's SPARK balance might be low. It tries SPARK first and automatically retries with SOGNI if insufficient.
1021
1046
 
1022
1047
  ## High-Res Video Routing
1023
1048
 
@@ -1032,7 +1057,7 @@ When the user asks for video in **"hd"**, **"1080p"**, **"4k"**, **"uhd"**, or *
1032
1057
  - Rewrite the user's request using the **LTX-2.3 Prompt Rule** before invoking the command. Do not send short slogan-style prompts to LTX.
1033
1058
  - Treat "4k" as a signal to use the highest practical LTX path exposed by this skill, even though the current wrapper caps non-WAN video dimensions at 2048px on the long side.
1034
1059
 
1035
- **Security:** Agents must use the CLI's built-in flags (`--extract-last-frame`, `--concat-videos`, `--list-media`) for all file operations and video manipulation. Never run raw shell commands (`ffmpeg`, `ls`, `cp`, etc.) directly.
1060
+ **Security:** Agents must use the CLI's built-in flags (`--extract-last-frame`, `--extract-first-frame`, `--concat-videos`, `--remix-audio`, `--list-media`) for all file operations and video/audio manipulation. Never run raw shell commands (`ffmpeg`, `ls`, `cp`, etc.) directly.
1036
1061
 
1037
1062
  ## Animate Between Two Images (First-Frame / Last-Frame)
1038
1063
 
@@ -1071,6 +1096,50 @@ When the final stitched output needs a single external soundtrack, add `--concat
1071
1096
  - User says "animate this video to this image" → extract last frame, use as `--ref`, target image as `--ref-end`, then stitch
1072
1097
  - User says "continue this video" with a target image → same as above
1073
1098
 
1099
+ ### Transition Between Two Videos (Bridge Clip)
1100
+
1101
+ When a user asks to **create a transition between two existing videos** (A → B), bridge them with a generated clip anchored on both boundary frames:
1102
+
1103
+ 1. **Extract the last frame of video A** and the **first frame of video B**:
1104
+ ```bash
1105
+ sogni-agent --extract-last-frame ./videoA.mp4 ./A_last.png
1106
+ sogni-agent --extract-first-frame ./videoB.mp4 ./B_first.png
1107
+ ```
1108
+ 2. **Generate the transition** with i2v, anchoring start→end so both seams are clean. Match `--fps` to the surrounding clips:
1109
+ ```bash
1110
+ sogni-agent -q --video -m wan_v2.2-14b-fp8_i2v_lightx2v \
1111
+ --ref ./A_last.png --ref-end ./B_first.png --fps 24 \
1112
+ -o ./transition.mp4 "descriptive morph between the two scenes"
1113
+ ```
1114
+ 3. **Concatenate A → transition → B**:
1115
+ ```bash
1116
+ sogni-agent --concat-videos ./merged.mp4 ./videoA.mp4 ./transition.mp4 ./videoB.mp4
1117
+ ```
1118
+
1119
+ > **i2v clips are silent and use the model's own frame rate** (often not 24). `--concat-videos` now normalizes fps/size and fills silent audio automatically, so mismatched clips stitch correctly — but passing `--fps` to the transition generation keeps things clean from the start. Use `--concat-fps <n>` to force a specific output frame rate.
1120
+
1121
+ ### Remix / Layer Audio After Stitching
1122
+
1123
+ After concatenating, use `--remix-audio` to rebuild the audio track **without re-encoding the video** (it is stream-copied, so it is fast and lossless on the picture). Combine the audio flags:
1124
+
1125
+ ```bash
1126
+ # Loop one clip's audio across the whole merged video and fade it out at the end
1127
+ sogni-agent --remix-audio ./merged.mp4 ./final.mp4 \
1128
+ --bed-audio ./clip1.mp4 --audio-loop --audio-fade-out 2
1129
+
1130
+ # Same, but also layer a second clip's original audio back in starting at 18s
1131
+ sogni-agent --remix-audio ./merged.mp4 ./final.mp4 \
1132
+ --bed-audio ./clip1.mp4 --audio-loop --audio-fade-out 2 \
1133
+ --mix-audio ./clip3.mp4 --mix-at 18.01 --mix-gain -3
1134
+ ```
1135
+
1136
+ - `--bed-audio` accepts a video or audio file; if omitted, the input video's own audio is the bed.
1137
+ - `--audio-loop` loops the bed to cover the full video; `--audio-fade-in` / `--audio-fade-out` fade it.
1138
+ - `--mix-audio` overlays one extra track (mixed at full level with a peak limiter so it never clips); position it with `--mix-at` and adjust level with `--mix-gain` (dB).
1139
+ - To mix more than two layers, chain `--remix-audio` passes (each only re-encodes audio).
1140
+
1141
+ **Do NOT run raw `ffmpeg` commands** for any of this. Use `--extract-first-frame`, `--extract-last-frame`, `--concat-videos`, and `--remix-audio`.
1142
+
1074
1143
  ## JSON Output
1075
1144
 
1076
1145
  ```json
@@ -556,7 +556,7 @@ const REPAIR_RECIPES = [
556
556
  "errorCode": "COST_LIMIT_EXCEEDED",
557
557
  "mode": "stopAndAsk",
558
558
  "maxRetries": 0,
559
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
559
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
560
560
  },
561
561
  {
562
562
  "recipeId": "edit_image.cost_limit_exceeded",
@@ -565,7 +565,7 @@ const REPAIR_RECIPES = [
565
565
  "errorCode": "COST_LIMIT_EXCEEDED",
566
566
  "mode": "stopAndAsk",
567
567
  "maxRetries": 0,
568
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
568
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
569
569
  },
570
570
  {
571
571
  "recipeId": "restore_photo.cost_limit_exceeded",
@@ -574,7 +574,7 @@ const REPAIR_RECIPES = [
574
574
  "errorCode": "COST_LIMIT_EXCEEDED",
575
575
  "mode": "stopAndAsk",
576
576
  "maxRetries": 0,
577
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
577
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
578
578
  },
579
579
  {
580
580
  "recipeId": "apply_style.cost_limit_exceeded",
@@ -583,7 +583,7 @@ const REPAIR_RECIPES = [
583
583
  "errorCode": "COST_LIMIT_EXCEEDED",
584
584
  "mode": "stopAndAsk",
585
585
  "maxRetries": 0,
586
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
586
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
587
587
  },
588
588
  {
589
589
  "recipeId": "refine_result.cost_limit_exceeded",
@@ -592,7 +592,7 @@ const REPAIR_RECIPES = [
592
592
  "errorCode": "COST_LIMIT_EXCEEDED",
593
593
  "mode": "stopAndAsk",
594
594
  "maxRetries": 0,
595
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
595
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
596
596
  },
597
597
  {
598
598
  "recipeId": "animate_photo.cost_limit_exceeded",
@@ -601,7 +601,7 @@ const REPAIR_RECIPES = [
601
601
  "errorCode": "COST_LIMIT_EXCEEDED",
602
602
  "mode": "stopAndAsk",
603
603
  "maxRetries": 0,
604
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
604
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
605
605
  },
606
606
  {
607
607
  "recipeId": "change_angle.cost_limit_exceeded",
@@ -610,7 +610,7 @@ const REPAIR_RECIPES = [
610
610
  "errorCode": "COST_LIMIT_EXCEEDED",
611
611
  "mode": "stopAndAsk",
612
612
  "maxRetries": 0,
613
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
613
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
614
614
  },
615
615
  {
616
616
  "recipeId": "generate_video.cost_limit_exceeded",
@@ -619,7 +619,7 @@ const REPAIR_RECIPES = [
619
619
  "errorCode": "COST_LIMIT_EXCEEDED",
620
620
  "mode": "stopAndAsk",
621
621
  "maxRetries": 0,
622
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
622
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
623
623
  },
624
624
  {
625
625
  "recipeId": "sound_to_video.cost_limit_exceeded",
@@ -628,7 +628,7 @@ const REPAIR_RECIPES = [
628
628
  "errorCode": "COST_LIMIT_EXCEEDED",
629
629
  "mode": "stopAndAsk",
630
630
  "maxRetries": 0,
631
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
631
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
632
632
  },
633
633
  {
634
634
  "recipeId": "video_to_video.cost_limit_exceeded",
@@ -637,7 +637,7 @@ const REPAIR_RECIPES = [
637
637
  "errorCode": "COST_LIMIT_EXCEEDED",
638
638
  "mode": "stopAndAsk",
639
639
  "maxRetries": 0,
640
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
640
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
641
641
  },
642
642
  {
643
643
  "recipeId": "generate_music.cost_limit_exceeded",
@@ -646,7 +646,7 @@ const REPAIR_RECIPES = [
646
646
  "errorCode": "COST_LIMIT_EXCEEDED",
647
647
  "mode": "stopAndAsk",
648
648
  "maxRetries": 0,
649
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
649
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
650
650
  },
651
651
  {
652
652
  "recipeId": "extend_video.cost_limit_exceeded",
@@ -655,7 +655,7 @@ const REPAIR_RECIPES = [
655
655
  "errorCode": "COST_LIMIT_EXCEEDED",
656
656
  "mode": "stopAndAsk",
657
657
  "maxRetries": 0,
658
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
658
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
659
659
  },
660
660
  {
661
661
  "recipeId": "replace_video_segment.cost_limit_exceeded",
@@ -664,7 +664,7 @@ const REPAIR_RECIPES = [
664
664
  "errorCode": "COST_LIMIT_EXCEEDED",
665
665
  "mode": "stopAndAsk",
666
666
  "maxRetries": 0,
667
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
667
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
668
668
  },
669
669
  {
670
670
  "recipeId": "overlay_video.cost_limit_exceeded",
@@ -673,7 +673,7 @@ const REPAIR_RECIPES = [
673
673
  "errorCode": "COST_LIMIT_EXCEEDED",
674
674
  "mode": "stopAndAsk",
675
675
  "maxRetries": 0,
676
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
676
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
677
677
  },
678
678
  {
679
679
  "recipeId": "add_subtitles.cost_limit_exceeded",
@@ -682,7 +682,7 @@ const REPAIR_RECIPES = [
682
682
  "errorCode": "COST_LIMIT_EXCEEDED",
683
683
  "mode": "stopAndAsk",
684
684
  "maxRetries": 0,
685
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
685
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
686
686
  },
687
687
  {
688
688
  "recipeId": "stitch_video.cost_limit_exceeded",
@@ -691,7 +691,7 @@ const REPAIR_RECIPES = [
691
691
  "errorCode": "COST_LIMIT_EXCEEDED",
692
692
  "mode": "stopAndAsk",
693
693
  "maxRetries": 0,
694
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
694
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
695
695
  },
696
696
  {
697
697
  "recipeId": "orbit_video.cost_limit_exceeded",
@@ -700,7 +700,7 @@ const REPAIR_RECIPES = [
700
700
  "errorCode": "COST_LIMIT_EXCEEDED",
701
701
  "mode": "stopAndAsk",
702
702
  "maxRetries": 0,
703
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
703
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
704
704
  },
705
705
  {
706
706
  "recipeId": "dance_montage.cost_limit_exceeded",
@@ -709,7 +709,7 @@ const REPAIR_RECIPES = [
709
709
  "errorCode": "COST_LIMIT_EXCEEDED",
710
710
  "mode": "stopAndAsk",
711
711
  "maxRetries": 0,
712
- "repairNoteTemplate": "You have hit the credit limit for this turn. Top up credits or wait for the daily refill."
712
+ "repairNoteTemplate": "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs"
713
713
  },
714
714
  {
715
715
  "recipeId": "generate_image.asset_not_found",
@@ -2550,6 +2550,38 @@ const PROMPT_CONTRACTS = [
2550
2550
  // '../public-skill-runtime/index.js' (and downstream consumers that still
2551
2551
  // reference the creative-agent subpath) keep working unchanged.
2552
2552
  export * from '@sogni-ai/sogni-intelligence-client/public-skill-runtime';
2553
+
2554
+ // PUBLIC_SKILL_SPARK_PACKS_COST_LIMIT_OVERRIDE
2555
+ import {
2556
+ PUBLIC_SKILL_DEFAULT_POLICIES as SOGNI_PUBLIC_SKILL_DEFAULT_POLICIES,
2557
+ PUBLIC_SKILL_DEFAULT_PROMPT_CONTRACTS as SOGNI_PUBLIC_SKILL_DEFAULT_PROMPT_CONTRACTS,
2558
+ PUBLIC_SKILL_DEFAULT_REPAIR_RECIPES as SOGNI_PUBLIC_SKILL_DEFAULT_REPAIR_RECIPES,
2559
+ createPublicSkillContractRuntime as createSogniPublicSkillContractRuntime,
2560
+ } from '@sogni-ai/sogni-intelligence-client/public-skill-runtime';
2561
+
2562
+ export const PUBLIC_SKILL_DEFAULT_REPAIR_RECIPES = SOGNI_PUBLIC_SKILL_DEFAULT_REPAIR_RECIPES.map((recipe) =>
2563
+ recipe.errorCode === 'COST_LIMIT_EXCEEDED'
2564
+ ? { ...recipe, message: "You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs" }
2565
+ : recipe
2566
+ );
2567
+
2568
+ export function createPublicSkillDefaultContractRuntime(input = {}) {
2569
+ return createSogniPublicSkillContractRuntime({
2570
+ policies: [
2571
+ ...SOGNI_PUBLIC_SKILL_DEFAULT_POLICIES,
2572
+ ...(input.policies ?? []),
2573
+ ],
2574
+ promptContracts: [
2575
+ ...SOGNI_PUBLIC_SKILL_DEFAULT_PROMPT_CONTRACTS,
2576
+ ...(input.promptContracts ?? []),
2577
+ ],
2578
+ repairRecipes: [
2579
+ ...PUBLIC_SKILL_DEFAULT_REPAIR_RECIPES,
2580
+ ...(input.repairRecipes ?? []),
2581
+ ],
2582
+ });
2583
+ }
2584
+
2553
2585
  // Moved to @sogni-ai/sogni-intelligence-client/skill-runtime-source in Phase 8.4 follow-up.
2554
2586
  // This file is kept as a thin re-export so existing internal `testing/`
2555
2587
  // callers keep working. New code should import from the public mid-tier:
@@ -0,0 +1,37 @@
1
+ // Zero-dependency Node.js version guard.
2
+ //
3
+ // This module is imported FIRST by sogni-agent.mjs (before `sharp` and the
4
+ // Sogni SDK). ES modules evaluate their dependencies in source order, so this
5
+ // runs — and can exit cleanly — before any modern-syntax/native dependency is
6
+ // loaded. That turns "wrong Node version" from a cryptic native/ESM stack
7
+ // trace into a one-line, actionable message.
8
+ //
9
+ // Keep this file import-free and syntactically conservative so it parses and
10
+ // runs on old Node versions too.
11
+
12
+ var MIN_NODE_VERSION = [22, 11, 0];
13
+
14
+ function isVersionAtLeast(current, required) {
15
+ for (var i = 0; i < required.length; i++) {
16
+ var currentValue = current[i] || 0;
17
+ var requiredValue = required[i] || 0;
18
+ if (currentValue > requiredValue) return true;
19
+ if (currentValue < requiredValue) return false;
20
+ }
21
+ return true;
22
+ }
23
+
24
+ try {
25
+ var raw = (process && process.versions && process.versions.node) || '0';
26
+ var current = String(raw).split('.').map(function (part) { return Number(part); });
27
+ if (!isVersionAtLeast(current, MIN_NODE_VERSION)) {
28
+ var required = MIN_NODE_VERSION.join('.');
29
+ process.stderr.write(
30
+ 'Error: Sogni requires Node.js >= ' + required + ' (you have ' + raw + ').\n' +
31
+ 'Hint: Upgrade Node — e.g. with nvm (`nvm install ' + MIN_NODE_VERSION[0] + '`), fnm, or volta — then re-run.\n'
32
+ );
33
+ process.exit(1);
34
+ }
35
+ } catch (err) {
36
+ // Never let the guard itself crash the CLI; fall through to normal startup.
37
+ }
@@ -2,7 +2,7 @@
2
2
  "id": "sogni-creative-agent-skill",
3
3
  "name": "Sogni Creative Agent Skill — Image, Video & Music Generation",
4
4
  "description": "Agent skill and CLI for Sogni AI image, video, and music generation.",
5
- "version": "3.3.5",
5
+ "version": "3.4.0",
6
6
  "skills": [
7
7
  "."
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sogni-ai/sogni-creative-agent-skill",
3
- "version": "3.3.5",
3
+ "version": "3.4.0",
4
4
  "description": "Sogni Creative Agent Skill: agent skill and CLI for Sogni AI image, video, and music generation.",
5
5
  "type": "module",
6
6
  "main": "sogni-agent.mjs",
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "openclaw:sync": "node scripts/sync-openclaw-plugin.mjs",
12
- "sync:creative-agent-runtime": "node ../sogni-creative-agent/scripts/sync-skill-runtime.mjs",
12
+ "sync:creative-agent-runtime": "node scripts/sync-creative-agent-runtime.mjs",
13
13
  "check:creative-agent-source": "node scripts/check-creative-agent-source.mjs",
14
14
  "check:creative-agent-source:strict": "node scripts/check-creative-agent-source.mjs --strict-network",
15
15
  "check:creative-agent-runtime": "node scripts/check-creative-agent-runtime.mjs",
@@ -56,6 +56,7 @@
56
56
  "skill-package.json",
57
57
  "scripts/check-creative-agent-runtime.mjs",
58
58
  "scripts/check-creative-agent-source.mjs",
59
+ "scripts/sync-creative-agent-runtime.mjs",
59
60
  "version.mjs",
60
61
  "scripts/sync-openclaw-plugin.mjs",
61
62
  "openclaw-plugin.mjs",
@@ -63,11 +64,12 @@
63
64
  "env.mjs",
64
65
  "ssrf-guard.mjs",
65
66
  "update-check.mjs",
67
+ "node-version-check.mjs",
66
68
  "generated/creative-agent-runtime.mjs",
67
69
  "sogni-agent.mjs"
68
70
  ],
69
71
  "dependencies": {
70
- "@sogni-ai/sogni-intelligence-client": "^3.0.11",
72
+ "@sogni-ai/sogni-intelligence-client": "^3.0.13",
71
73
  "execa": "^9.6.1",
72
74
  "json5": "^2.2.3",
73
75
  "sharp": "^0.34.5"
@@ -4,9 +4,7 @@ import { fileURLToPath } from 'node:url';
4
4
  import { spawnSync } from 'node:child_process';
5
5
 
6
6
  const repoRoot = dirname(dirname(fileURLToPath(import.meta.url)));
7
- const syncScript = process.env.SOGNI_CREATIVE_AGENT_SYNC_SCRIPT
8
- ? process.env.SOGNI_CREATIVE_AGENT_SYNC_SCRIPT
9
- : join(repoRoot, '..', 'sogni-creative-agent', 'scripts', 'sync-skill-runtime.mjs');
7
+ const syncScript = join(repoRoot, 'scripts', 'sync-creative-agent-runtime.mjs');
10
8
  const generatedPath = join(repoRoot, 'generated', 'creative-agent-runtime.mjs');
11
9
  const generatedRelativePath = 'generated/creative-agent-runtime.mjs';
12
10
 
@@ -0,0 +1,98 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ const repoRoot = dirname(dirname(fileURLToPath(import.meta.url)));
7
+ const upstreamSyncScript = process.env.SOGNI_CREATIVE_AGENT_SYNC_SCRIPT
8
+ ? process.env.SOGNI_CREATIVE_AGENT_SYNC_SCRIPT
9
+ : join(repoRoot, '..', 'sogni-creative-agent', 'scripts', 'sync-skill-runtime.mjs');
10
+ const generatedPath = join(repoRoot, 'generated', 'creative-agent-runtime.mjs');
11
+
12
+ const STALE_COST_LIMIT_REPAIR_NOTE =
13
+ 'You have hit the credit limit for this turn. Top up credits or wait for the daily refill.';
14
+ const SPARK_PACKS_COST_LIMIT_REPAIR_NOTE =
15
+ 'You have hit the credit limit for this turn. Buy Spark Packs to continue: https://docs.sogni.ai/pricing/#spark-packs';
16
+ const PUBLIC_RUNTIME_REEXPORT = "export * from '@sogni-ai/sogni-intelligence-client/public-skill-runtime';";
17
+ const PUBLIC_RUNTIME_OVERRIDE_MARKER = 'PUBLIC_SKILL_SPARK_PACKS_COST_LIMIT_OVERRIDE';
18
+
19
+ function run(command, args, options = {}) {
20
+ const result = spawnSync(command, args, {
21
+ cwd: repoRoot,
22
+ encoding: 'utf8',
23
+ ...options,
24
+ });
25
+ if (result.error) throw result.error;
26
+ return result;
27
+ }
28
+
29
+ function applyPublicRuntimeOverrides(source) {
30
+ let updated = source.replaceAll(STALE_COST_LIMIT_REPAIR_NOTE, SPARK_PACKS_COST_LIMIT_REPAIR_NOTE);
31
+ if (updated.includes(PUBLIC_RUNTIME_REEXPORT) && !updated.includes(PUBLIC_RUNTIME_OVERRIDE_MARKER)) {
32
+ const costLimitMessage = JSON.stringify(SPARK_PACKS_COST_LIMIT_REPAIR_NOTE);
33
+ const overrideBlock = `
34
+ // ${PUBLIC_RUNTIME_OVERRIDE_MARKER}
35
+ import {
36
+ PUBLIC_SKILL_DEFAULT_POLICIES as SOGNI_PUBLIC_SKILL_DEFAULT_POLICIES,
37
+ PUBLIC_SKILL_DEFAULT_PROMPT_CONTRACTS as SOGNI_PUBLIC_SKILL_DEFAULT_PROMPT_CONTRACTS,
38
+ PUBLIC_SKILL_DEFAULT_REPAIR_RECIPES as SOGNI_PUBLIC_SKILL_DEFAULT_REPAIR_RECIPES,
39
+ createPublicSkillContractRuntime as createSogniPublicSkillContractRuntime,
40
+ } from '@sogni-ai/sogni-intelligence-client/public-skill-runtime';
41
+
42
+ export const PUBLIC_SKILL_DEFAULT_REPAIR_RECIPES = SOGNI_PUBLIC_SKILL_DEFAULT_REPAIR_RECIPES.map((recipe) =>
43
+ recipe.errorCode === 'COST_LIMIT_EXCEEDED'
44
+ ? { ...recipe, message: ${costLimitMessage} }
45
+ : recipe
46
+ );
47
+
48
+ export function createPublicSkillDefaultContractRuntime(input = {}) {
49
+ return createSogniPublicSkillContractRuntime({
50
+ policies: [
51
+ ...SOGNI_PUBLIC_SKILL_DEFAULT_POLICIES,
52
+ ...(input.policies ?? []),
53
+ ],
54
+ promptContracts: [
55
+ ...SOGNI_PUBLIC_SKILL_DEFAULT_PROMPT_CONTRACTS,
56
+ ...(input.promptContracts ?? []),
57
+ ],
58
+ repairRecipes: [
59
+ ...PUBLIC_SKILL_DEFAULT_REPAIR_RECIPES,
60
+ ...(input.repairRecipes ?? []),
61
+ ],
62
+ });
63
+ }
64
+ `;
65
+ updated = updated.replace(PUBLIC_RUNTIME_REEXPORT, `${PUBLIC_RUNTIME_REEXPORT}\n${overrideBlock}`);
66
+ }
67
+ return updated;
68
+ }
69
+
70
+ if (!existsSync(upstreamSyncScript)) {
71
+ console.error(`Missing Sogni Creative Agent runtime sync script: ${upstreamSyncScript}`);
72
+ console.error('Set SOGNI_CREATIVE_AGENT_SYNC_SCRIPT or check out sogni-creative-agent as a sibling repo.');
73
+ process.exit(1);
74
+ }
75
+
76
+ const syncResult = run(process.execPath, [upstreamSyncScript], {
77
+ env: {
78
+ ...process.env,
79
+ SOGNI_CREATIVE_AGENT_SKILL_DIR: repoRoot,
80
+ },
81
+ stdio: 'inherit',
82
+ });
83
+ if (syncResult.status !== 0) {
84
+ process.exit(syncResult.status || 1);
85
+ }
86
+
87
+ if (!existsSync(generatedPath)) {
88
+ console.error('Runtime sync completed but generated/creative-agent-runtime.mjs was not created.');
89
+ process.exit(1);
90
+ }
91
+
92
+ const generated = readFileSync(generatedPath, 'utf8');
93
+ const updated = applyPublicRuntimeOverrides(generated);
94
+ if (updated !== generated) {
95
+ mkdirSync(dirname(generatedPath), { recursive: true });
96
+ writeFileSync(generatedPath, updated);
97
+ console.log(`Applied public skill runtime overrides to ${generatedPath}`);
98
+ }
@@ -3,7 +3,7 @@
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@sogni-ai/sogni-intelligence-client": "^3.0.11",
6
+ "@sogni-ai/sogni-intelligence-client": "^3.0.13",
7
7
  "execa": "^9.6.1",
8
8
  "json5": "^2.2.3",
9
9
  "sharp": "^0.34.5"