Bundle
dsh-mxpage
MxPage for DeepSeek Harness: ecommerce product-image workbench (analyze → plan → Visual Prompt Agent → generate) on top of an extracted, host-agnostic core of ziguishian/MxPage.
- Source
- kim1232aa
- License
- MIT
- Updated
- Updated 15 hours ago
Readme
# dsh-mxpage
English | [简体中文](./README.zh-CN.md)
**DeepSeek Harness plugin** that brings [MxPage](https://github.com/ziguishian/MxPage)'s
ecommerce product-image workbench into the official DSH **`web`** profile:
**register photos → analyze selling points → plan hero/detail sections → Visual Prompt Agent → generate → edit → export**
This is **not** a generic gallery, and it is **not** a thin prompt wrapper.
Community plugins such as `dsh-imagegen` register a one-shot `generate_image`.
**dsh-mxpage does not.** Every tool is named `mxpage_*`, and the model must walk
**analyze → plan → VPA → generate** so a product photo becomes a coherent
detail-page set rather than a pile of unrelated images.
Verified against **DSH `0.1.2-rc.1`** host packages. Requires the official
**`web`** profile (not `sdk-minimal`).
**Not an official DeepSeek product.** Not affiliated with DeepSeek AI.
MxPage prompts/schemas remain MIT (灵矩绘境); see [NOTICE](./NOTICE).
---
## Architecture — "换芯留壳"
v0.1 reimplemented MxPage's pipeline by hand and threw away the parts that make
it a product. v0.2 inverts that: the upstream **kernel is ported**, the DSH
adapter is **thin**, and the upstream **UI is in scope**.
```
src/core/ host-agnostic port of upstream lib/ (never imports @deepseek-ai/*)
└── ports/ Repository · ProviderResolver · Logger · StorageDriver · TaskRunner
src/host/ the five port implementations (JSON repository, fs storage, channels,
jobs-backed task runner)
src/shared/ route paths shared by host and browser
src/tools/ thin mxpage_* wrappers over core services
src/client/ the browser panel
```
`src/core/**` cannot import `@deepseek-ai/*`, `schemastery`, Next.js, Prisma or
React, and cannot touch `process.cwd()` / `process.env` — enforced by a test.
Long-running work goes through **`ctx.jobs`** (`@deepseek-ai/dsh-jobs-local`),
so the shell owns job identity, session scoping, lifecycle state, completion
notices and owner-disposal cancellation. `src/host/task-runner.ts` is the
fallback for hosts without a registry — the plugin still boots either way,
because `jobs` is read through the optional `ctx.get('jobs')` accessor.
Why the extraction was cheap, with evidence:
| Fact | Evidence |
|---|---|
| Zero `next/*` imports inside upstream `lib/` | the one exception, `provider-runtime.ts`, imported `NextRequest` solely to read two headers |
| `@prisma/client` appears 9 times | two are type-only; four use only the `Prisma` namespace |
| The 1344-line OpenAI adapter had **one** hard coupling | `import { inferCategory, logApiUsage } from "@/lib/monitor/api-usage"` |
See [`src/core/README.md`](src/core/README.md) for the full seam list.
---
## Configuration — channels, not an env var
v0.1 required `MXPAGE_IMAGE_API_KEY`. v0.2 uses a **channel list**, so several
endpoints can be configured and rotated:
**设置 → 插件 → MxPage → 渠道**, or in `cordis.patch.yml`:
```yaml
- insert:
- id: mxpage
name: dsh-mxpage
config:
channels:
- id: xai
label: xAI (Grok)
baseUrl: https://api.example.com/v1
apiKeyEnv: MXPAGE_XAI_KEY # preferred: keeps the secret out of the doc
models: [grok-imagine-image-2.0]
textModel: grok-4
imageModel: grok-imagine-image-2.0
rotateChannelOnQuotaExhausted: true
```
| Field | Meaning |
|---|---|
| `id` / `label` | rotation key and display name |
| `baseUrl` | OpenAI-compatible base URL; a missing `/v1` is retried automatically |
| `apiKey` / `apiKeyEnv` | literal secret, or (preferred) the **name** of an env var |
| `models` | explicit image model ids; leave empty to discover via `GET /models` |
| `textModel` / `imageModel` | preferred models for analyze+plan / generate on this channel |
| `disabled` | skip this channel without deleting it |
Run **`mxpage_channels`** first whenever something fails — it reports which
channel is active, its catalog, and whether any image-capable model was found.
> **Model capability is inferred from the model name.** Upstream deliberately
> skips real endpoint probing to avoid burning image quota, so "the name looks
> like an image model" does not prove the gateway can render images. A gateway
> that advertises a model it cannot serve is discovered by *failing*.
---
## Paid Images API — read this first
Generation and edit tools call an **OpenAI-compatible Images API**
(`/images/generations`, `/images/edits`). That API is **paid**. Each
`mxpage_generate_section`, `mxpage_generate_page` and `mxpage_edit_section`
consumes quota. The shipped skills instruct the model to confirm with you before
a whole-page job.
Secrets never appear in tool output, logs or session events — `sk-` and `Bearer`
tokens are redacted (`src/util/redact.ts`).
---
## Install
```sh
# from a checkout
dsh plugin add link:/absolute/path/to/mxpage
# or from the built tarball
dsh plugin add ./dsh-mxpage-0.3.0.tgz
```
`dsh plugin add` registers the bundle in the profile's
`dsh.profile.bundles` for you. Then configure a channel (above) and restart the
profile so the layer loads.
### Skills catalog
The skills ship inside the package. Copy them next to the DSH catalog so they
are discoverable:
```sh
mkdir -p "${DSH_HOME:-$HOME/.dsh}/skills"
cp -R skills/mxpage-ecommerce-page skills/mxpage-xiaohongshu skills/mxpage-batch-sku \
"${DSH_HOME:-$HOME/.dsh}/skills/"
```
---
## Chat example
Attach a product photo and say:
> 根据这张商品图出一套淘宝详情页
Expected: the agent runs create → analyze → plan → generate and you get a hero
set plus detail sections, each as a **new version** (never overwriting), stored
under `$DSH_HOME/mxpage/projects/<projectId>/` and returned as attachments.
---
## Panel — the eight screens
The plugin ships a browser half (`lib/client.js`) that mounts a workbench into
the DSH centre column, with a sidebar toggle. This is the part upstream MxPage
actually lives in: a stateful workspace, not a prompt wrapper.
| Screen | What it does |
|---|---|
| **分析** | Project meta (name / platform / style, delete project), the product-asset grid (upload, reorder, set main image, delete), and the structured analysis editor with one-click analyze / save |
| **规划** | Output config (hero/detail counts, aspect, in-image language), analyze → plan, the project-level visual style guide, the section list with per-section generation and a whole-page job, plus whole-page translation into a target language |
| **编辑** | Per-section preview, inline editing of title/goal/copy/visualPrompt, generate / regenerate / repaint / enhance / translate, and the version list with activate |
| **导出** | One-click ZIP / JSON export, export notes, the model snapshot, and a gallery of everything currently exportable |
| **小红书** | The four-step carousel flow: plan → review each `imagePrompt` → generate → edit, with per-page download |
| **批量 SKU** | Up to 20 product images per batch — one project per SKU, optional background analyze+plan with per-SKU failure isolation |
| **监控** | The API usage ledger: totals, token counts, top models / projects, quota-state classification, per-entry delete / clear, plus task history with retry for failed tasks |
| **渠道** | Channel diagnostics: the active channel, its model catalog, image/vision/text counts, per-channel key presence, connection test and model discovery with role recommendations, and the quota-rotation note |
Re-planning is guarded behind an explicit confirmation, because it deletes every
section, version and generated image in the project.
**Build-format note.** The DSH web shell does **not** load client halves as ESM.
It hands each bundle a `window.__ModuleLoader__.load({ id, factory })` façade and
a `require` that resolves the shell's live module table. `tsdown` therefore emits
the browser half as CJS into `lib/client.raw.cjs`, and
`scripts/wrap-client.mjs` wraps it into the envelope. `test/client-bundle.test.ts`
loads the built bundle through a simulated façade and asserts `apply` + `inject`
come back — that test exists because an ESM bundle would silently never apply.
The panel's data API lives at `/api/dsh-mxpage/*` (`src/host/routes.ts`),
registered on the host `webServer` and fenced to loopback requests.
---
## Tools (all `mxpage_*`)
| Tool | Role |
|---|---|
| `mxpage_create_project` | New project from 1–10 photos via `attachment_ids` (chat) and/or `image_paths` |
| `mxpage_add_asset` | Append a photo; `role: "main"` swaps the primary reference |
| `mxpage_project_status` | Read-only: analysis, sections, versions, running tasks |
| `mxpage_analyze_product` | Vision analysis → category, materials, selling points, suggested plan |
| `mxpage_plan_page` | Section plan + project-level `visualStyleGuide`. **Re-planning deletes existing sections and images** |
| `mxpage_generate_section` | One frame; runs the VPA unless `prompt_override` is set |
| `mxpage_edit_section` | `repaint` / `enhance` / `translate`; new version, never overwrites |
| `mxpage_generate_page` | Whole page as a background job; `mode: "missing"` fills gaps only |
| `mxpage_job_status` / `mxpage_job_cancel` | Job control; completed sections stay on disk |
| `mxpage_export_page` | ZIP (`00-头图/` + `01-详情页/` + `export-manifest.json`) or project JSON |
| `mxpage_xiaohongshu_plan` | Xiaohongshu step 1 — has a fully local Chinese fallback plan |
| `mxpage_xiaohongshu_generate` | Step 3 — one image per page, VPA-gated |
| `mxpage_xiaohongshu_edit` | Step 4 — edit one page in place |
| `mxpage_translate_page` | Whole-page translation as a background job — one `translate` edit per generated section |
| `mxpage_update_project` | Rename a project or change its platform / style |
| `mxpage_delete_project` | Delete a project and its workspace; requires `confirm: true` |
| `mxpage_set_main_asset` | Swap the primary reference image by asset id or image path |
| `mxpage_usage_stats` | Usage-ledger summary: calls, tokens, quota events, top models / projects, recent errors |
| `mxpage_channels` | Channel diagnostics |
---
## Skills
| Skill | When |
|---|---|
| `mxpage-ecommerce-page` | Taobao / Tmall / JD / Shopee hero + detail pages |
| `mxpage-xiaohongshu` | The four-step carousel flow |
| `mxpage-batch-sku` | One project per SKU; never mix reference images |
---
## Differences from upstream, and fixes made during the port
**Fixed** (each documented at its call site):
1. **Visual Prompt Agent retry bug.** Upstream `requestRaw` read
`if (urls.length === 1 || options?.suppressUsageLog)`, conflating "skip usage
logging" with "skip the base-URL retry". The VPA is the only caller passing
`suppressUsageLog: true`, so it silently lost the `/v1`-vs-root fallback and
degraded to the template prompt on gateways needing a versioned base URL.
2. **Path traversal.** Upstream's `/api/files/[...path]` route joined
`rootDir()` with an unvalidated relative path. The storage port rejects
escapes (`normalizeRelPath`) and the route is gone entirely.
3. **Cross-platform paths.** Upstream stored `path.join` output (backslashes on
Windows) while its URL builder converted back with `split(path.sep)`.
4. **Production cancellation.** Upstream's abort registry was guarded by
`process.env.NODE_ENV !== "production"`, so production builds could not cancel.
5. **Dead values dropped:** `ProjectStatus.COMPLETED` and
`GenerationStatus.QUEUED` were never written by any upstream service.
**Preserved deliberately** (flagged, not silently changed):
- **Quota does not rotate models.** Upstream `shouldFallbackToNextImageModel`
returns false for `429 / quota / 403 / 401`, so an exhausted channel aborts
instead of trying the next candidate. Exposed as the
`rotateChannelOnQuotaExhausted` config flag.
- **`editSectionImage` is not cancellable** — upstream never registered an abort
controller on that path.
- `archiver` was replaced by a dependency-free ZIP writer, which also removes
upstream's `process.cwd()` temp file.
---
## Develop
```sh
npm install
npm run build # tsdown → lib/index.js (+ lib/client.js when src/client exists)
npm test # node --experimental-strip-types --test
npx tsc --noEmit # 0 errors expected
```
Tests worth knowing about:
- `test/bundle.test.ts` — package manifest sanity **and** the host-agnostic
invariant on `src/core/**`
- `test/core.test.ts` — ZIP header/CRC format, repository semantics
(terminal-state stickiness, system-project hiding, stale recovery), storage
path containment, task cancellation
- `test/smoke.test.ts` — loads the **built** `lib/index.js`, runs the real
`apply()` against a mock Cordis context, and asserts all 20 tools register
---
## License
MIT. MxPage prompts, schemas and pipeline logic are MIT (灵矩绘境) — see
[NOTICE](./NOTICE) and [LICENSE](./LICENSE).
Topics: `dsh-plugin` · `dsh` · `deepseek-harness` · `mxpage` · `ecommerce`
Install
dsh plugin --profile web add github:kim1232aa/dsh-mxpage
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-mxpage from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.