@acedatacloud/skills 2026.622.3 → 2026.627.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/AGENTS.md +0 -1
- package/README.md +2 -4
- package/package.json +1 -2
- package/skills/_shared/mcp-servers.md +0 -1
- package/skills/acedatacloud-api/SKILL.md +1 -1
- package/skills/bilibili/scripts/bilibili.py +122 -1
- package/skills/csdn/scripts/csdn.py +127 -2
- package/skills/midjourney-image/SKILL.md +0 -170
package/AGENTS.md
CHANGED
|
@@ -12,7 +12,6 @@ Skills are located in the `skills/` directory (also mirrored to `.agents/skills/
|
|
|
12
12
|
- **fish-audio** — Text-to-speech and voice synthesis with Fish Audio
|
|
13
13
|
|
|
14
14
|
### AI Image Generation
|
|
15
|
-
- **midjourney-image** — Generate, edit, blend, describe, and upscale images with Midjourney
|
|
16
15
|
- **flux-image** — Generate and edit images with Flux (Black Forest Labs)
|
|
17
16
|
- **seedream-image** — Generate and edit images with ByteDance Seedream
|
|
18
17
|
- **nano-banana-image** — Generate and edit images with Google Gemini (NanoBanana)
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
Compatible with **30+ AI coding agents** via the [agentskills.io](https://agentskills.io/) open standard: Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, Cursor, Roo Code, Goose, and more.
|
|
14
14
|
|
|
15
|
-
## Available Skills (
|
|
15
|
+
## Available Skills (29)
|
|
16
16
|
|
|
17
17
|
### AI Music & Audio
|
|
18
18
|
|
|
@@ -26,7 +26,6 @@ Compatible with **30+ AI coding agents** via the [agentskills.io](https://agents
|
|
|
26
26
|
|
|
27
27
|
| Skill | Description |
|
|
28
28
|
|-------|-------------|
|
|
29
|
-
| [midjourney-image](skills/midjourney-image/) | Generate, edit, blend, describe, and upscale images with Midjourney |
|
|
30
29
|
| [flux-image](skills/flux-image/) | Generate and edit images with Flux (Black Forest Labs) |
|
|
31
30
|
| [seedream-image](skills/seedream-image/) | Generate and edit images with ByteDance Seedream |
|
|
32
31
|
| [nano-banana-image](skills/nano-banana-image/) | Generate and edit images with Google Gemini (NanoBanana) |
|
|
@@ -92,7 +91,7 @@ export ACEDATACLOUD_API_TOKEN="your-token-here"
|
|
|
92
91
|
npx skills add acedatacloud/skills --skill '*' --yes
|
|
93
92
|
|
|
94
93
|
# Install specific skills only
|
|
95
|
-
npx skills add acedatacloud/skills --skill suno-music --skill
|
|
94
|
+
npx skills add acedatacloud/skills --skill suno-music --skill flux-image --yes
|
|
96
95
|
```
|
|
97
96
|
|
|
98
97
|
The [`skills` CLI](https://github.com/vercel-labs/skills) auto-detects your agent and installs to the correct path.
|
|
@@ -197,7 +196,6 @@ Skills provide **knowledge** (when to use, parameters, gotchas). MCP servers pro
|
|
|
197
196
|
| Skill | MCP Server | Install | Hosted Endpoint |
|
|
198
197
|
|-------|-----------|---------|-----------------|
|
|
199
198
|
| suno-music | [mcp-suno](https://pypi.org/project/mcp-suno/) | `pip install mcp-suno` | `https://suno.mcp.acedata.cloud/mcp` |
|
|
200
|
-
| midjourney-image | [mcp-midjourney](https://pypi.org/project/mcp-midjourney/) | `pip install mcp-midjourney` | `https://midjourney.mcp.acedata.cloud/mcp` |
|
|
201
199
|
| google-search | [mcp-serp](https://pypi.org/project/mcp-serp/) | `pip install mcp-serp` | `https://serp.mcp.acedata.cloud/mcp` |
|
|
202
200
|
| flux-image | [mcp-flux-pro](https://pypi.org/project/mcp-flux-pro/) | `pip install mcp-flux-pro` | `https://flux.mcp.acedata.cloud/mcp` |
|
|
203
201
|
| luma-video | [mcp-luma](https://pypi.org/project/mcp-luma/) | `pip install mcp-luma` | `https://luma.mcp.acedata.cloud/mcp` |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acedatacloud/skills",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.627.0",
|
|
4
4
|
"description": "Agent Skills for AceDataCloud AI services — music, image, video generation, LLM chat, web search. Compatible with Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, and 30+ AI coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"openai-codex",
|
|
13
13
|
"ai-tools",
|
|
14
14
|
"suno",
|
|
15
|
-
"midjourney",
|
|
16
15
|
"flux",
|
|
17
16
|
"luma",
|
|
18
17
|
"sora",
|
|
@@ -8,7 +8,6 @@ Each AceDataCloud service has a corresponding MCP server that provides tool-use
|
|
|
8
8
|
|-------|---------|-----------------|
|
|
9
9
|
| suno-music | `pip install mcp-suno` | `https://suno.mcp.acedata.cloud/mcp` |
|
|
10
10
|
| producer-music | — | — |
|
|
11
|
-
| midjourney-image | `pip install mcp-midjourney` | `https://midjourney.mcp.acedata.cloud/mcp` |
|
|
12
11
|
| google-search | `pip install mcp-serp` | `https://serp.mcp.acedata.cloud/mcp` |
|
|
13
12
|
| flux-image | `pip install mcp-flux-pro` | `https://flux.mcp.acedata.cloud/mcp` |
|
|
14
13
|
| luma-video | `pip install mcp-luma` | `https://luma.mcp.acedata.cloud/mcp` |
|
|
@@ -112,7 +112,7 @@ Error response format:
|
|
|
112
112
|
| Category | Services | Base Path |
|
|
113
113
|
|----------|----------|-----------|
|
|
114
114
|
| **AI Chat** | GPT, Claude, Gemini, Kimi, Grok | `/v1/chat/completions` |
|
|
115
|
-
| **Image Gen** |
|
|
115
|
+
| **Image Gen** | Flux, Seedream, NanoBanana | `/flux/*`, `/seedream/*`, etc. |
|
|
116
116
|
| **Video Gen** | Luma, Sora, Veo, Kling, Hailuo, Seedance, Wan | `/luma/*`, `/sora/*`, etc. |
|
|
117
117
|
| **Music Gen** | Suno, Producer, Fish Audio | `/suno/*`, `/producer/*`, `/fish/*` |
|
|
118
118
|
| **Search** | Google Search (web/images/news/maps) | `/serp/*` |
|
|
@@ -25,6 +25,7 @@ from __future__ import annotations
|
|
|
25
25
|
import argparse
|
|
26
26
|
import gzip
|
|
27
27
|
import hashlib
|
|
28
|
+
import html as _html
|
|
28
29
|
import ipaddress
|
|
29
30
|
import json
|
|
30
31
|
import os
|
|
@@ -287,7 +288,7 @@ _IMG_SKIP = ("hdslb.com", "bilibili.com", "biliimg.com")
|
|
|
287
288
|
# matches both HTML <img src="..."> and markdown 
|
|
288
289
|
# <img ... src = "..."> / '...' (tolerates spaces and either quote style)
|
|
289
290
|
_HTML_IMG = re.compile(r"""(<img\b[^>]*?\bsrc\s*=\s*)(["'])(https?://[^"']+)(\2)""", re.IGNORECASE)
|
|
290
|
-
_MD_IMG = re.compile(r"!\[([^\]]
|
|
291
|
+
_MD_IMG = re.compile(r"!\[([^\]]{0,500})\]\((https?://[^)\s]{0,2000})\)") # bounded: no O(n²) on "![" * N
|
|
291
292
|
UPCOVER = "https://api.bilibili.com/x/article/creative/article/upcover"
|
|
292
293
|
|
|
293
294
|
|
|
@@ -429,6 +430,121 @@ def rehost_images(jar, csrf, content):
|
|
|
429
430
|
return content
|
|
430
431
|
|
|
431
432
|
|
|
433
|
+
# ── Markdown → HTML (专栏 body is HTML; convert if the caller passed Markdown) ──
|
|
434
|
+
|
|
435
|
+
# Bounded so a single token can't scan to EOF — keeps these linear (no O(n²)
|
|
436
|
+
# backtracking on malformed input like "![" * N). Real alt < 500 / URL < 2000.
|
|
437
|
+
_IMG_RE = re.compile(r"!\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)")
|
|
438
|
+
_LINK_RE = re.compile(r"(?<!!)\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)")
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _looks_like_markdown(s):
|
|
442
|
+
# Three-way classification (no full parser): block-level HTML ⇒ already an
|
|
443
|
+
# HTML document → pass through (even if it also contains markdown-looking
|
|
444
|
+
# text like **bold**); else markdown markers ⇒ render; else inline-only HTML
|
|
445
|
+
# ⇒ pass through; else plain text ⇒ render (md_to_html just wraps/escapes it).
|
|
446
|
+
s = s or ""
|
|
447
|
+
block_html = re.search(
|
|
448
|
+
r"</?(?:p|div|h[1-6]|ul|ol|li|table|thead|tbody|tr|td|th|blockquote|"
|
|
449
|
+
r"pre|figure|figcaption|section|article|header|footer|nav|aside|hr|"
|
|
450
|
+
r"main|details|summary)\b",
|
|
451
|
+
s, re.I,
|
|
452
|
+
)
|
|
453
|
+
if block_html:
|
|
454
|
+
return False
|
|
455
|
+
# ^-anchored (re.M) + horizontal-only whitespace + bounded {0,N} repeats so
|
|
456
|
+
# the scan can't backtrack across newlines or to EOF (no quadratic scanning).
|
|
457
|
+
has_md = re.search(
|
|
458
|
+
r"^#{1,6}\s|!\[[^\]]{0,500}\]\([^)]{0,2000}\)|^[ \t]*[-*+]\s|^[ \t]*\d+\.\s"
|
|
459
|
+
r"|\[[^\]]{0,500}\]\([^)]{0,2000}\)|`[^`]{1,500}`|\*\*[^*]{1,500}\*\*",
|
|
460
|
+
s, re.M,
|
|
461
|
+
)
|
|
462
|
+
if has_md:
|
|
463
|
+
return True
|
|
464
|
+
inline_html = re.search(
|
|
465
|
+
r"</?(?:a|strong|em|b|i|u|s|span|code|br|img|small|mark|sup|sub|"
|
|
466
|
+
r"video|audio|iframe)\b",
|
|
467
|
+
s, re.I,
|
|
468
|
+
)
|
|
469
|
+
return not inline_html
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _attr_url(u):
|
|
473
|
+
# Strip C0 controls/space FIRST, then allow only http/https/mailto — otherwise
|
|
474
|
+
# `\x01javascript:` would slip past a literal scheme check yet be re-normalised
|
|
475
|
+
# to javascript: by the browser. Finally neutralise attribute-breaking quotes.
|
|
476
|
+
u = re.sub(r"[\x00-\x20\x7f]", "", u or "")
|
|
477
|
+
m = re.match(r"(?i)([a-z][a-z0-9+.\-]*):", u)
|
|
478
|
+
if m and m.group(1).lower() not in ("http", "https", "mailto"):
|
|
479
|
+
return "#"
|
|
480
|
+
return u.replace('"', "%22").replace("'", "%27")
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _alt(s):
|
|
484
|
+
return (s or "").replace('"', """).replace("'", "'")
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _inline_md(t):
|
|
488
|
+
t = _html.escape(t, quote=False).replace("\x00", "")
|
|
489
|
+
# Stash code spans FIRST so emphasis/link/image markup inside `…` stays
|
|
490
|
+
# literal (e.g. `**x**` must render as text, not bold).
|
|
491
|
+
spans = []
|
|
492
|
+
|
|
493
|
+
def _stash(m):
|
|
494
|
+
spans.append("<code>" + m.group(1) + "</code>")
|
|
495
|
+
return f"\x00{len(spans) - 1}\x00"
|
|
496
|
+
|
|
497
|
+
t = re.sub(r"`([^`]+)`", _stash, t)
|
|
498
|
+
t = _IMG_RE.sub(lambda m: f'<img src="{_attr_url(m.group(2))}" alt="{_alt(m.group(1))}">', t)
|
|
499
|
+
t = _LINK_RE.sub(lambda m: f'<a href="{_attr_url(m.group(2))}">{m.group(1)}</a>', t)
|
|
500
|
+
t = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", t)
|
|
501
|
+
t = re.sub(r"(?<!\*)\*([^*\n]+)\*(?!\*)", r"<em>\1</em>", t)
|
|
502
|
+
t = re.sub(r"\x00(\d+)\x00", lambda m: spans[int(m.group(1))], t)
|
|
503
|
+
return t
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _md_block_to_html(b):
|
|
507
|
+
b = b.strip("\n")
|
|
508
|
+
if not b.strip():
|
|
509
|
+
return None
|
|
510
|
+
f = b.lstrip()
|
|
511
|
+
m = re.match(r"(#{1,6})\s+(.*)", f)
|
|
512
|
+
if m:
|
|
513
|
+
lvl = len(m.group(1))
|
|
514
|
+
return f"<h{lvl}>{_inline_md(m.group(2).strip())}</h{lvl}>"
|
|
515
|
+
if f.startswith(">"):
|
|
516
|
+
inner = re.sub(r"^>\s?", "", b, flags=re.M).replace("\n", " ")
|
|
517
|
+
return "<blockquote><p>" + _inline_md(inner) + "</p></blockquote>"
|
|
518
|
+
if re.match(r"[-*+]\s+", f):
|
|
519
|
+
items = [_inline_md(re.sub(r"^[-*+]\s+", "", ln)) for ln in b.split("\n") if ln.strip()]
|
|
520
|
+
return "<ul>" + "".join(f"<li>{x}</li>" for x in items) + "</ul>"
|
|
521
|
+
if re.match(r"\d+\.\s+", f):
|
|
522
|
+
items = [_inline_md(re.sub(r"^\d+\.\s+", "", ln)) for ln in b.split("\n") if ln.strip()]
|
|
523
|
+
return "<ol>" + "".join(f"<li>{x}</li>" for x in items) + "</ol>"
|
|
524
|
+
only = re.fullmatch(r"!\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)", f)
|
|
525
|
+
if only:
|
|
526
|
+
return f'<img src="{_attr_url(only.group(2))}" alt="{_alt(_html.escape(only.group(1), quote=False))}">'
|
|
527
|
+
return "<p>" + _inline_md(b.replace("\n", " ").strip()) + "</p>"
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def md_to_html(src):
|
|
531
|
+
src = (src or "").strip()
|
|
532
|
+
out = []
|
|
533
|
+
# Pull fenced code out FIRST (it may itself contain blank lines) so the
|
|
534
|
+
# blank-line block splitter below can never tear a ``` … ``` fence apart.
|
|
535
|
+
for i, part in enumerate(re.split(r"(?ms)^(```.*?(?:\n```[ \t]*$|\Z))", src)):
|
|
536
|
+
if i % 2 == 1: # fenced code segment
|
|
537
|
+
code = re.sub(r"\A```[^\n]*\n?", "", part)
|
|
538
|
+
code = re.sub(r"\n?```[ \t]*\Z", "", code)
|
|
539
|
+
out.append("<pre><code>" + _html.escape(code) + "</code></pre>")
|
|
540
|
+
continue
|
|
541
|
+
for b in re.split(r"\n[ \t]*\n", part): # blank-line split (no cross-newline \s*)
|
|
542
|
+
block = _md_block_to_html(b)
|
|
543
|
+
if block:
|
|
544
|
+
out.append(block)
|
|
545
|
+
return "\n".join(out)
|
|
546
|
+
|
|
547
|
+
|
|
432
548
|
def cmd_publish(jar, args):
|
|
433
549
|
if not args.title:
|
|
434
550
|
die("--title is required")
|
|
@@ -461,6 +577,11 @@ def cmd_publish(jar, args):
|
|
|
461
577
|
if not args.no_rehost_images:
|
|
462
578
|
content = rehost_images(jar, csrf, content)
|
|
463
579
|
|
|
580
|
+
# 专栏 content is HTML; if the caller passed Markdown, render it (else the
|
|
581
|
+
# article shows literal `##`/`![]()` and runs together as one paragraph).
|
|
582
|
+
if _looks_like_markdown(content):
|
|
583
|
+
content = md_to_html(content)
|
|
584
|
+
|
|
464
585
|
ref = "https://member.bilibili.com/"
|
|
465
586
|
base = {
|
|
466
587
|
"title": args.title, "content": content, "csrf": csrf,
|
|
@@ -28,6 +28,7 @@ import base64
|
|
|
28
28
|
import gzip
|
|
29
29
|
import hashlib
|
|
30
30
|
import hmac
|
|
31
|
+
import html as _html
|
|
31
32
|
import ipaddress
|
|
32
33
|
import json
|
|
33
34
|
import mimetypes
|
|
@@ -286,7 +287,7 @@ def cmd_article(jar, args):
|
|
|
286
287
|
# Signed signature → Huawei OBS multipart → csdnimg.cn URL. Mirrors our
|
|
287
288
|
# PlatformPublisher csdn.py flow.
|
|
288
289
|
|
|
289
|
-
_MD_IMG = re.compile(r"!\[([^\]]
|
|
290
|
+
_MD_IMG = re.compile(r"!\[([^\]]{0,500})\]\((https?://[^)\s]{0,2000})\)") # bounded: no O(n²) on "![" * N
|
|
290
291
|
_IMG_SKIP = ("csdnimg.cn", "csdn.net")
|
|
291
292
|
|
|
292
293
|
|
|
@@ -416,6 +417,126 @@ def rehost_images(jar, content):
|
|
|
416
417
|
return _MD_IMG.sub(repl, content)
|
|
417
418
|
|
|
418
419
|
|
|
420
|
+
# ── Markdown → HTML (CSDN renders the `content` HTML field, not the source) ──
|
|
421
|
+
|
|
422
|
+
# Bounded so a single token can't scan to EOF — keeps these linear (no O(n²)
|
|
423
|
+
# backtracking on malformed input like "![" * N). Real alt < 500 / URL < 2000.
|
|
424
|
+
_IMG_RE = re.compile(r"!\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)")
|
|
425
|
+
_LINK_RE = re.compile(r"(?<!!)\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)")
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _attr_url(u):
|
|
429
|
+
# Strip C0 controls/space FIRST, then allow only http/https/mailto — otherwise
|
|
430
|
+
# `\x01javascript:` would slip past a literal scheme check yet be re-normalised
|
|
431
|
+
# to javascript: by the browser. Finally neutralise attribute-breaking quotes.
|
|
432
|
+
u = re.sub(r"[\x00-\x20\x7f]", "", u or "")
|
|
433
|
+
m = re.match(r"(?i)([a-z][a-z0-9+.\-]*):", u)
|
|
434
|
+
if m and m.group(1).lower() not in ("http", "https", "mailto"):
|
|
435
|
+
return "#"
|
|
436
|
+
return u.replace('"', "%22").replace("'", "%27")
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _alt(s):
|
|
440
|
+
# alt text is already &/</>-escaped by the line escape; only the attribute
|
|
441
|
+
# quote can still break out, so neutralise it.
|
|
442
|
+
return (s or "").replace('"', """).replace("'", "'")
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _inline_md(t):
|
|
446
|
+
t = _html.escape(t, quote=False).replace("\x00", "")
|
|
447
|
+
# Stash code spans FIRST so emphasis/link/image markup inside `…` stays
|
|
448
|
+
# literal (e.g. `**x**` must render as text, not bold).
|
|
449
|
+
spans = []
|
|
450
|
+
|
|
451
|
+
def _stash(m):
|
|
452
|
+
spans.append("<code>" + m.group(1) + "</code>")
|
|
453
|
+
return f"\x00{len(spans) - 1}\x00"
|
|
454
|
+
|
|
455
|
+
t = re.sub(r"`([^`]+)`", _stash, t)
|
|
456
|
+
t = _IMG_RE.sub(lambda m: f'<img src="{_attr_url(m.group(2))}" alt="{_alt(m.group(1))}">', t)
|
|
457
|
+
t = _LINK_RE.sub(lambda m: f'<a href="{_attr_url(m.group(2))}">{m.group(1)}</a>', t)
|
|
458
|
+
t = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", t)
|
|
459
|
+
t = re.sub(r"(?<!\*)\*([^*\n]+)\*(?!\*)", r"<em>\1</em>", t)
|
|
460
|
+
t = re.sub(r"\x00(\d+)\x00", lambda m: spans[int(m.group(1))], t)
|
|
461
|
+
return t
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _md_block_to_html(b):
|
|
465
|
+
b = b.strip("\n")
|
|
466
|
+
if not b.strip():
|
|
467
|
+
return None
|
|
468
|
+
f = b.lstrip()
|
|
469
|
+
m = re.match(r"(#{1,6})\s+(.*)", f)
|
|
470
|
+
if m:
|
|
471
|
+
lvl = len(m.group(1))
|
|
472
|
+
return f"<h{lvl}>{_inline_md(m.group(2).strip())}</h{lvl}>"
|
|
473
|
+
if f.startswith(">"):
|
|
474
|
+
inner = re.sub(r"^>\s?", "", b, flags=re.M).replace("\n", " ")
|
|
475
|
+
return "<blockquote><p>" + _inline_md(inner) + "</p></blockquote>"
|
|
476
|
+
if re.match(r"[-*+]\s+", f):
|
|
477
|
+
items = [_inline_md(re.sub(r"^[-*+]\s+", "", ln)) for ln in b.split("\n") if ln.strip()]
|
|
478
|
+
return "<ul>" + "".join(f"<li>{x}</li>" for x in items) + "</ul>"
|
|
479
|
+
if re.match(r"\d+\.\s+", f):
|
|
480
|
+
items = [_inline_md(re.sub(r"^\d+\.\s+", "", ln)) for ln in b.split("\n") if ln.strip()]
|
|
481
|
+
return "<ol>" + "".join(f"<li>{x}</li>" for x in items) + "</ol>"
|
|
482
|
+
only = re.fullmatch(r"!\[([^\]]{0,500})\]\(([^)\s]{0,2000})\)", f)
|
|
483
|
+
if only:
|
|
484
|
+
return f'<img src="{_attr_url(only.group(2))}" alt="{_alt(_html.escape(only.group(1), quote=False))}">'
|
|
485
|
+
return "<p>" + _inline_md(b.replace("\n", " ").strip()) + "</p>"
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def md_to_html(src):
|
|
489
|
+
"""Minimal stdlib Markdown→HTML for the platforms whose body is HTML.
|
|
490
|
+
Covers headings, paragraphs, standalone images, links, bold/italic/code,
|
|
491
|
+
fenced code, blockquotes, and ordered/unordered lists."""
|
|
492
|
+
src = (src or "").strip()
|
|
493
|
+
out = []
|
|
494
|
+
# Pull fenced code out FIRST (it may itself contain blank lines) so the
|
|
495
|
+
# blank-line block splitter below can never tear a ``` … ``` fence apart.
|
|
496
|
+
for i, part in enumerate(re.split(r"(?ms)^(```.*?(?:\n```[ \t]*$|\Z))", src)):
|
|
497
|
+
if i % 2 == 1: # fenced code segment
|
|
498
|
+
code = re.sub(r"\A```[^\n]*\n?", "", part)
|
|
499
|
+
code = re.sub(r"\n?```[ \t]*\Z", "", code)
|
|
500
|
+
out.append("<pre><code>" + _html.escape(code) + "</code></pre>")
|
|
501
|
+
continue
|
|
502
|
+
for b in re.split(r"\n[ \t]*\n", part): # blank-line split (no cross-newline \s*)
|
|
503
|
+
block = _md_block_to_html(b)
|
|
504
|
+
if block:
|
|
505
|
+
out.append(block)
|
|
506
|
+
return "\n".join(out)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _looks_like_markdown(s):
|
|
510
|
+
# Three-way classification (no full parser): block-level HTML ⇒ already an
|
|
511
|
+
# HTML document → pass through (even if it also contains markdown-looking
|
|
512
|
+
# text like **bold**); else markdown markers ⇒ render; else inline-only HTML
|
|
513
|
+
# ⇒ pass through; else plain text ⇒ render (md_to_html just wraps/escapes it).
|
|
514
|
+
s = s or ""
|
|
515
|
+
block_html = re.search(
|
|
516
|
+
r"</?(?:p|div|h[1-6]|ul|ol|li|table|thead|tbody|tr|td|th|blockquote|"
|
|
517
|
+
r"pre|figure|figcaption|section|article|header|footer|nav|aside|hr|"
|
|
518
|
+
r"main|details|summary)\b",
|
|
519
|
+
s, re.I,
|
|
520
|
+
)
|
|
521
|
+
if block_html:
|
|
522
|
+
return False
|
|
523
|
+
# ^-anchored (re.M) + horizontal-only whitespace + bounded {0,N} repeats so
|
|
524
|
+
# the scan can't backtrack across newlines or to EOF (no quadratic scanning).
|
|
525
|
+
has_md = re.search(
|
|
526
|
+
r"^#{1,6}\s|!\[[^\]]{0,500}\]\([^)]{0,2000}\)|^[ \t]*[-*+]\s|^[ \t]*\d+\.\s"
|
|
527
|
+
r"|\[[^\]]{0,500}\]\([^)]{0,2000}\)|`[^`]{1,500}`|\*\*[^*]{1,500}\*\*",
|
|
528
|
+
s, re.M,
|
|
529
|
+
)
|
|
530
|
+
if has_md:
|
|
531
|
+
return True
|
|
532
|
+
inline_html = re.search(
|
|
533
|
+
r"</?(?:a|strong|em|b|i|u|s|span|code|br|img|small|mark|sup|sub|"
|
|
534
|
+
r"video|audio|iframe)\b",
|
|
535
|
+
s, re.I,
|
|
536
|
+
)
|
|
537
|
+
return not inline_html
|
|
538
|
+
|
|
539
|
+
|
|
419
540
|
def cmd_publish(jar, args):
|
|
420
541
|
if not args.title:
|
|
421
542
|
die("--title is required")
|
|
@@ -450,8 +571,12 @@ def cmd_publish(jar, args):
|
|
|
450
571
|
status_code = 2 if args.draft_only else 0
|
|
451
572
|
body = {
|
|
452
573
|
"title": args.title,
|
|
574
|
+
# markdowncontent = the editor source; content = the RENDERED HTML CSDN
|
|
575
|
+
# actually displays. Sending raw markdown as `content` shows literal
|
|
576
|
+
# `##`/`![]()` and collapses newlines — it MUST be HTML. Already-HTML
|
|
577
|
+
# callers are passed through untouched (don't double-escape).
|
|
453
578
|
"markdowncontent": content,
|
|
454
|
-
"content": content,
|
|
579
|
+
"content": md_to_html(content) if _looks_like_markdown(content) else content,
|
|
455
580
|
"readType": "public",
|
|
456
581
|
"status": status_code,
|
|
457
582
|
"categories": "",
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: midjourney-image
|
|
3
|
-
description: Generate, edit, blend, upscale, and describe images with Midjourney via AceDataCloud API. Use when creating AI images from text prompts, editing existing images, generating 2x2 grids, upscaling, creating variations, blending multiple images, reverse-prompting from images, or generating video from images. Supports versions 5.2 through 8.
|
|
4
|
-
license: Apache-2.0
|
|
5
|
-
metadata:
|
|
6
|
-
author: acedatacloud
|
|
7
|
-
version: "1.0"
|
|
8
|
-
compatibility: Requires ACEDATACLOUD_API_TOKEN in .env file (see _shared/authentication.md). Optionally pair with mcp-midjourney for tool-use.
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Midjourney Image Generation
|
|
12
|
-
|
|
13
|
-
Generate and manipulate AI images through AceDataCloud's Midjourney API.
|
|
14
|
-
|
|
15
|
-
> **Setup:** See [authentication](../_shared/authentication.md) for token setup.
|
|
16
|
-
|
|
17
|
-
## Quick Start — Generate an Image
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
curl -X POST https://api.acedata.cloud/midjourney/imagine \
|
|
21
|
-
-H "Authorization: Bearer $ACEDATACLOUD_API_TOKEN" \
|
|
22
|
-
-H "Content-Type: application/json" \
|
|
23
|
-
-d '{"prompt": "a futuristic city at sunset, cyberpunk style --ar 16:9", "callback_url": "https://api.acedata.cloud/health"}'
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
> **Async:** See [async task polling](../_shared/async-tasks.md). Poll via `POST /midjourney/tasks` with `{"id": "..."}`.
|
|
27
|
-
|
|
28
|
-
## Generation Modes
|
|
29
|
-
|
|
30
|
-
| Mode | Speed | Cost | Best For |
|
|
31
|
-
|------|-------|------|----------|
|
|
32
|
-
| `fast` | Fast | Standard | Most tasks (default) |
|
|
33
|
-
| `relax` | Slow | Cheaper | Batch generation |
|
|
34
|
-
| `turbo` | Fastest | Premium | Time-sensitive work |
|
|
35
|
-
|
|
36
|
-
## Midjourney Versions
|
|
37
|
-
|
|
38
|
-
| Version | Notes |
|
|
39
|
-
|---------|-------|
|
|
40
|
-
| `8` | Latest, best quality |
|
|
41
|
-
| `7` | Great quality, fast |
|
|
42
|
-
| `6.1` | Stable, well-tested |
|
|
43
|
-
| `6` | Previous generation |
|
|
44
|
-
| `5.2` | Legacy |
|
|
45
|
-
|
|
46
|
-
## Core Workflows
|
|
47
|
-
|
|
48
|
-
### 1. Generate Images (Imagine)
|
|
49
|
-
|
|
50
|
-
Creates a 2x2 grid of 4 image variations.
|
|
51
|
-
|
|
52
|
-
```json
|
|
53
|
-
POST /midjourney/imagine
|
|
54
|
-
{
|
|
55
|
-
"prompt": "a serene mountain lake at dawn, photorealistic --ar 16:9 --v 7",
|
|
56
|
-
"mode": "fast",
|
|
57
|
-
"translation": true,
|
|
58
|
-
"split_images": true
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
Set `translation: true` to auto-translate non-English prompts. Set `split_images: true` to get individual images besides the grid.
|
|
63
|
-
|
|
64
|
-
### 2. Upscale / Vary / Pan / Zoom
|
|
65
|
-
|
|
66
|
-
After generating a grid, use transform actions on individual images:
|
|
67
|
-
|
|
68
|
-
```json
|
|
69
|
-
POST /midjourney/imagine
|
|
70
|
-
{
|
|
71
|
-
"action": "upscale1",
|
|
72
|
-
"image_id": "grid-image-id"
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**Available actions:**
|
|
77
|
-
- `upscale1`–`upscale4`: Upscale individual quadrant
|
|
78
|
-
- `variation1`–`variation4`: Create variation of a quadrant
|
|
79
|
-
- `variation_subtle` / `variation_strong`: Subtle/strong variation of full image
|
|
80
|
-
- `reroll`: Re-generate with same prompt
|
|
81
|
-
- `zoom_out_2x` / `zoom_out_1_5x`: Zoom out
|
|
82
|
-
- `pan_left` / `pan_right` / `pan_up` / `pan_down`: Extend canvas
|
|
83
|
-
|
|
84
|
-
### 3. Edit an Image
|
|
85
|
-
|
|
86
|
-
Modify an existing image using a text prompt, optionally with a mask.
|
|
87
|
-
|
|
88
|
-
```json
|
|
89
|
-
POST /midjourney/edits
|
|
90
|
-
{
|
|
91
|
-
"image_url": "https://example.com/photo.jpg",
|
|
92
|
-
"prompt": "add a rainbow in the sky",
|
|
93
|
-
"mode": "fast"
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### 4. Blend Images
|
|
98
|
-
|
|
99
|
-
Combine 2–5 images into a new composition.
|
|
100
|
-
|
|
101
|
-
```json
|
|
102
|
-
POST /midjourney/imagine
|
|
103
|
-
{
|
|
104
|
-
"action": "blend",
|
|
105
|
-
"image_urls": [
|
|
106
|
-
"https://example.com/image1.jpg",
|
|
107
|
-
"https://example.com/image2.jpg"
|
|
108
|
-
]
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### 5. Describe an Image (Reverse Prompt)
|
|
113
|
-
|
|
114
|
-
Get AI-generated text descriptions of an image (returns 4 options).
|
|
115
|
-
|
|
116
|
-
```json
|
|
117
|
-
POST /midjourney/describe
|
|
118
|
-
{"image_url": "https://example.com/photo.jpg"}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### 6. Generate Video from Image
|
|
122
|
-
|
|
123
|
-
Create a video with a reference image and text prompt.
|
|
124
|
-
|
|
125
|
-
```json
|
|
126
|
-
POST /midjourney/videos
|
|
127
|
-
{
|
|
128
|
-
"image_url": "https://example.com/photo.jpg",
|
|
129
|
-
"prompt": "the city comes alive with moving traffic",
|
|
130
|
-
"resolution": "720p"
|
|
131
|
-
}
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
## Prompt Parameters
|
|
135
|
-
|
|
136
|
-
Append these to your prompt text:
|
|
137
|
-
|
|
138
|
-
| Parameter | Example | Description |
|
|
139
|
-
|-----------|---------|-------------|
|
|
140
|
-
| `--ar` | `--ar 16:9` | Aspect ratio |
|
|
141
|
-
| `--v` | `--v 7` | Midjourney version |
|
|
142
|
-
| `--q` | `--q 2` | Quality (0.25, 0.5, 1, 2) |
|
|
143
|
-
| `--s` | `--s 750` | Stylization (0–1000) |
|
|
144
|
-
| `--c` | `--c 50` | Chaos/variety (0–100) |
|
|
145
|
-
| `--no` | `--no text, watermark` | Negative prompt |
|
|
146
|
-
| `--seed` | `--seed 12345` | Reproducible generation |
|
|
147
|
-
|
|
148
|
-
## API-Level Parameters (Billing Impact)
|
|
149
|
-
|
|
150
|
-
These top-level fields on `POST /midjourney/imagine` affect billing and are separate from inline prompt parameters:
|
|
151
|
-
|
|
152
|
-
| Parameter | Type | Description |
|
|
153
|
-
|-----------|------|-------------|
|
|
154
|
-
| `version` | string | Midjourney version (`"8"`, `"7"`, `"6.1"`, etc.) — used for billing calculation |
|
|
155
|
-
| `hd` | boolean | Enable HD 2K resolution (V8 only) — costs 4× GPU time |
|
|
156
|
-
| `quality` | string | Quality level: `".25"`, `".5"`, `"1"`, `"2"`, `"4"` — quality `"4"` is V8 only and costs 4× GPU time |
|
|
157
|
-
| `style_reference` | boolean | Whether prompt uses `--sref` style references (V8: costs 4× GPU time) |
|
|
158
|
-
| `moodboard` | boolean | Whether prompt uses moodboard image references (V8: costs 4× GPU time) |
|
|
159
|
-
|
|
160
|
-
## Gotchas
|
|
161
|
-
|
|
162
|
-
- Imagine returns a **2x2 grid** — use upscale/variation actions to work with individual images
|
|
163
|
-
- Use `split_images: true` to also receive individual cropped images alongside the grid
|
|
164
|
-
- Prompt parameters (`--ar`, `--v`, etc.) go **inside the prompt string**, not as separate fields
|
|
165
|
-
- `translation: true` auto-translates Chinese/other languages to English before sending to Midjourney
|
|
166
|
-
- Video generation requires a reference `image_url` — it cannot generate from text alone
|
|
167
|
-
- Available transform actions depend on the image — check `available_actions` in the response
|
|
168
|
-
- Get the seed with `POST /midjourney/seed` using the image_id for reproducible results
|
|
169
|
-
|
|
170
|
-
> **MCP:** `pip install mcp-midjourney` | Hosted: `https://midjourney.mcp.acedata.cloud/mcp` | See [all MCP servers](../_shared/mcp-servers.md)
|