Skip to content

v0.2 redesign: provider abstraction + dual-host + wizard + 2 real-account providers verified#1

Merged
Nowhitestar merged 80 commits into
mainfrom
claude/fervent-elgamal-6cdea0
May 6, 2026
Merged

v0.2 redesign: provider abstraction + dual-host + wizard + 2 real-account providers verified#1
Nowhitestar merged 80 commits into
mainfrom
claude/fervent-elgamal-6cdea0

Conversation

@Nowhitestar
Copy link
Copy Markdown
Owner

Summary

Major refactor (v0.1 scripts → v0.2 architecture). 77 commits across 4 plans plus real-account verification fixes.

Architecture (Plan 1): core/ host-agnostic Python (manifest, provider registry, age-encrypted credential vault, run lifecycle, platform rules) + providers/<name>/ filesystem-drop providers + scripts/mmp.py unified CLI + mmp console script entry point. Spec §3.5 6 architectural constraints enforced (verified by grep).

Wizard (Plan 2): 3-stage Claude-driven manifest builder (source extraction → target selection → manifest assembly) + credential setup flow. Markdown prompt fragments at core/wizard/*.md, thin Python helpers (loader, context, commit). mmp wizard --dump-context returns JSON for Claude to consume.

5 bundled providers (Plans 1 + 3): wechat-article (real WeChat OA API), xiaohongshu (local draft via xhs skill), wechat-image (browser-flow guide), x-article, substack (payload + TODO stubs). Spec §10 migration complete; old v0.1 scripts deprecated as thin shims.

Distribution + CI (Plan 4): .claude-plugin/plugin.json for Claude Code marketplace, GitHub Actions matrix (ubuntu + macos × py3.10/3.11/3.12), 5 user-facing docs (architecture, provider-contract, credentials, safety-policy, manual-verification), CHANGELOG, README.

Real-account verified:

  • ✅ wechat-article: real draft created on mp.weixin.qq.com (media_id WbX2nHWvJ4Nnr7sz...); 4 integration bugs found+fixed (asset path resolution, placeholder PNG, IP whitelist, etc.)
  • ✅ xiaohongshu: real local draft via skill's draft.sh at ~/.xiaohongshu/drafts/; 3 integration bugs found+fixed (path discovery, invocation contract, empty required_credentials in cmd_publish)
  • 7 real-world bugs that mock-based tests didn't catch — see docs/HANDOFF.md "Discovered during real-account verification"

v0.3 first task surfaced: split-routing networks see different outbound IPs to WeChat vs ipinfo (115.199.114.116 vs 103.129.180.55 in our verification). 50-IP whitelist will exhaust. v0.3 will ship WECHAT_API_PROXY env + Cloudflare Worker template.

Test Plan

  • make test — lint (ruff) + format-check + typecheck (mypy) + unit (102 passed) + smoke (scripts/test_local.py)
  • mmp doctor clean
  • mmp list providers shows all 5
  • mmp validate examples/{longform,longform-multi,image-post}.yaml all pass
  • mmp publish examples/longform.yaml (dry-run) → result.json status=ok
  • mmp publish examples/longform.yaml --mode-override draft → real WeChat draft on platform
  • mmp publish examples/image-post.yaml --mode-override draft → real local xhs draft
  • mmp publish examples/longform-multi.yaml --mode-override publish → all 3 targets blocked by capability gate (publish disabled)
  • mmp publish ... --mode-override draft without vault → graceful failure with structured error field, no leaked secrets
  • CI matrix green on all 6 jobs (ubuntu + macos × 3.10/3.11/3.12) — verifies on push
  • mmp doctor clean on a fresh machine after pip install -e . — manual

Known follow-ups (v0.3 backlog in docs/HANDOFF.md)

P0:

  • WeChat API proxy support (split-routing IP pain)
  • Vault hardening (atomic write + flock + lost-key UX)

P1:

  • mmp resume real implementation (interface ready)
  • Real connectors for x-article / substack (currently stubs)
  • Manual verification of remaining 3 providers (wechat-image guide / x-article TODO / substack TODO)

P2:

  • Drop deprecation shims (6 scripts)
  • User-folder provider trust prompt (docs claim it works; not implemented)
  • Various polish from per-plan reviews

Spec / Plan refs

  • Spec: docs/superpowers/specs/2026-05-05-multi-media-publisher-redesign-design.md
  • Plans: docs/superpowers/plans/2026-05-05-plan-{1,2,3,4}-*.md
  • HANDOFF: docs/HANDOFF.md

🤖 Generated with Claude Code

Nowhitestar and others added 30 commits May 5, 2026 22:28
Provider plugin abstraction + dual-host (CC plugin + OpenClaw) +
conversational manifest wizard + unified credential vault, scoped for
plugin-marketplace distribution.
Plan 1: core scaffold (manifest/provider/credentials/run/rules/host/errors)
        + wechat_article migration end-to-end (~19 tasks)
Plan 2: wizard flow — 4 prompt fragments + loader/context/commit + settings
        + mmp wizard subcommand (~13 tasks)
Plan 3: remaining 4 providers (xiaohongshu, wechat_image, x_article, substack)
        + deprecation shims + integration tests (~12 tasks)
Plan 4: dual-host distribution (.claude-plugin) + CI matrix + user docs
        (architecture/provider-contract/credentials/safety-policy/manual-verification)
        + CHANGELOG + version bump (~13 tasks)

Each plan has bite-sized TDD tasks with full code, exact file paths, and
expected command output. Self-review covered: spec coverage (all of §3-§11),
type consistency across plans, no placeholders.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d_keys for env-only

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the v0.1 WeChat OA helper from scripts/wechat_api_draft.py into
providers/wechat_article/internal/wechat_api.py as a pure Python module.

- Drop argparse / __main__ entry; expose 4 public functions:
  get_access_token, upload_thumb, add_draft, draft_from_payload
- Replace die() / SystemExit with proper exceptions (ValueError,
  RuntimeError, FileNotFoundError)
- Keep urllib-based HTTP semantics intact (URLs, payload shapes,
  multipart boundary, response parsing all preserved)
- Mark internal helpers with leading underscore
- Replace scripts/wechat_api_draft.py with a deprecation shim that
  warns + exits 1, scheduled for removal in v0.3
- Add requests>=2.31 to pyproject.toml dependencies for upcoming
  provider work

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolve 36 ruff errors so make test runs cleanly. Auto-fixes covered
unused imports (F401), import sorting (I001), .format() to f-string
(UP032). Manual fixes: dropped unused sub_doctor assignment in
scripts/mmp.py, hoisted unittest.mock.patch to top of test_provider.py
(E402), and added v0.1 deprecated scripts to ruff exclude list in
pyproject.toml (per Plan 3 deprecation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The example manifests referenced relative body/asset paths that didn't
exist in examples/, so the README and SKILL.md quickstart commands
failed. Add schema_version 0.2, update fields, and ship the supporting
article.md, caption.md, cover.png, img-01.png, img-02.png so
`mmp validate examples/longform.yaml` passes out of the box.
SKILL.md and README.md link to docs/safety-policy.md but the file did
not exist, leaving the safety contract dangling. Author a v0.2-aware
policy synthesized from the spec and references/publishing-policy.md so
both entry points resolve correctly.
…tainment

Per spec section 3.5(6) the run dir must be self-contained — but the
publish command copied the source manifest byte-for-byte, preserving
relative `body: ./article.md` references that point outside the run dir.
Resume and forensics broke if the source moved or was deleted.

Re-emit the manifest with the already-loaded body inlined so the run dir
stands on its own, mirroring what the lock JSON already does. Add a
regression test that asserts the AI Agent body content lives directly in
the run-dir manifest.yaml.
Nowhitestar added 28 commits May 6, 2026 10:51
Previous title "v0.2 image-post example" was 23 chars; xiaohongshu
TITLE_TOO_LONG rule rejects validate. Replaced with "v0.2 图文示例" (8
chars). Now `mmp validate examples/image-post.yaml` passes.

Found during dry-run acceptance testing.
…solute

Caught during real-account verification of wechat-article: provider.execute
was getting `Path("./cover.png")` and failing FileNotFoundError because CWD
was the repo root, not the manifest's directory.

`_resolve_asset_path` mirrors `_resolve_inline_or_path` semantics:
- `./` and `../` prefixes resolve relative to manifest dir
- Absolute paths pass through
- `http://` URLs pass through (future extension hook)
- None passes through

Also replaces examples/cover.png with a real 900×500 PNG. The previous 1x1
placeholder PNG (81 bytes) tripped WeChat's `errcode: 40113 unsupported
file type` on `material/add_material?type=thumb`.

Verified end-to-end: dry-run + real WeChat OA draft API now both green.
First successful real `mode_actual: draft-platform` with a valid media_id.
…ed_keys path

Caught during real-account verification of xhs local-draft. v0.2 implementation
diverged from the actual xiaohongshu skill's draft.sh in three ways:

1. Path discovery — searched ~/.openclaw/skills/ but real install is at
   ~/.openclaw/workspace/skills/. Added that path + XHS_DRAFT_SH env
   override for arbitrary script locations.

2. Invocation contract — passed `--payload <file> --cookie <file>` flags,
   but draft.sh expects a single positional JSON string and does NOT take
   a cookie (local draft writes to ~/.xiaohongshu/drafts/, no API call).
   Now build the JSON inline and pass as $1.

3. Output parsing — draft.sh emits human-readable stdout starting with
   "✓ 已创建本地草稿: <abs path>", not JSON. Parse the path with regex
   and derive draft_id from the basename.

4. Field mapping — draft.sh wants {title, content, images, tags}; mmp's
   payload.json uses {title, caption, ...}. Added _build_xhs_payload to
   reshape; payload.json on disk keeps caption (mmp-canonical) untouched.

Side fix in scripts/mmp.py cmd_publish: when a provider's
required_credentials is [] (e.g. xhs local-draft needs no creds), skip
the vault lookup entirely. Previously `store.get(..., required_keys=[])`
treated [] as None-ish and fell through to "vault has nothing" → raised
MissingCredentialError(['*']).

End-to-end verified: real local xhs draft created at
~/.xiaohongshu/drafts/<ts>-<slug>.json with absolute image paths.
… found

After running the full manual-verification checklist for wechat-article
(real WeChat OA API draft) and xiaohongshu (real local draft via skill),
record:
- 4 of 6 verification items passed
- 7 integration bugs found and fixed (see HANDOFF "Discovered during
  real-account verification")
- 1 operational pain (split-routing IP whitelist) → v0.3 first task is
  WeChat API proxy support
CI caught 4 files needing format that local `make test` missed because
the Makefile only ran `ruff check .`, not `ruff format --check .`.
Files reformatted: core/cli.py, providers/xiaohongshu/provider.py,
providers/xiaohongshu/tests/test_provider.py, scripts/mmp.py.

Also adds `ruff format --check .` to the Makefile lint target so this
mismatch doesn't recur.
@Nowhitestar Nowhitestar merged commit c7d64b5 into main May 6, 2026
6 checks passed
Nowhitestar added a commit that referenced this pull request May 7, 2026
…ount providers verified (#1)

* docs: add v0.2 redesign spec

Provider plugin abstraction + dual-host (CC plugin + OpenClaw) +
conversational manifest wizard + unified credential vault, scoped for
plugin-marketplace distribution.

* docs: add v0.2 implementation plans (1-4)

Plan 1: core scaffold (manifest/provider/credentials/run/rules/host/errors)
        + wechat_article migration end-to-end (~19 tasks)
Plan 2: wizard flow — 4 prompt fragments + loader/context/commit + settings
        + mmp wizard subcommand (~13 tasks)
Plan 3: remaining 4 providers (xiaohongshu, wechat_image, x_article, substack)
        + deprecation shims + integration tests (~12 tasks)
Plan 4: dual-host distribution (.claude-plugin) + CI matrix + user docs
        (architecture/provider-contract/credentials/safety-policy/manual-verification)
        + CHANGELOG + version bump (~13 tasks)

Each plan has bite-sized TDD tasks with full code, exact file paths, and
expected command output. Self-review covered: spec coverage (all of §3-§11),
type consistency across plans, no placeholders.

* chore: v0.2 project scaffolding

* chore: dedupe gitignore + ignore egg-info

* feat(core): add MMPError exception hierarchy

* feat(core): add host module for XDG paths and host detection

* feat(core): add PlatformRules + Violation

* feat(core): add Manifest schema, loading, normalization

* fix(manifest): error on missing ./body path instead of silent fallthrough

* feat(core): add CredentialStore with age FileBackend + EnvBackend

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(credentials): drop env-prefix scan; require explicit required_keys for env-only

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(core): add Provider ABC and ProviderRegistry

* refactor(provider): type violations list, harden import error, document trust_user

* feat(core): add Run lifecycle with result/log/checkpoint

* fix(run): collision-safe run dir, ASCII-only slug, empty-targets handling, asdict

* feat(providers): scaffold wechat_article provider

* refactor(wechat_article): move API helper to provider internal

Move the v0.1 WeChat OA helper from scripts/wechat_api_draft.py into
providers/wechat_article/internal/wechat_api.py as a pure Python module.

- Drop argparse / __main__ entry; expose 4 public functions:
  get_access_token, upload_thumb, add_draft, draft_from_payload
- Replace die() / SystemExit with proper exceptions (ValueError,
  RuntimeError, FileNotFoundError)
- Keep urllib-based HTTP semantics intact (URLs, payload shapes,
  multipart boundary, response parsing all preserved)
- Mark internal helpers with leading underscore
- Replace scripts/wechat_api_draft.py with a deprecation shim that
  warns + exits 1, scheduled for removal in v0.3
- Add requests>=2.31 to pyproject.toml dependencies for upcoming
  provider work

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(wechat_article): add platform rules

* feat(wechat_article): implement validate + prepare

* feat(wechat_article): implement execute + health_check

* feat(cli): add mmp.py with validate/publish/setup/list/resume/doctor

* test(integration): wechat_article dry-run end-to-end

* build: Makefile with lint/typecheck/unit/smoke; rewrite smoke for v0.2 CLI

* docs: rewrite SKILL.md for v0.2 architecture

* docs: update HANDOFF and README for v0.2 plan-1 progress

* chore: lint/typecheck cleanup

Resolve 36 ruff errors so make test runs cleanly. Auto-fixes covered
unused imports (F401), import sorting (I001), .format() to f-string
(UP032). Manual fixes: dropped unused sub_doctor assignment in
scripts/mmp.py, hoisted unittest.mock.patch to top of test_provider.py
(E402), and added v0.1 deprecated scripts to ruff exclude list in
pyproject.toml (per Plan 3 deprecation).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(examples): update to v0.2 schema with body/cover/caption assets

The example manifests referenced relative body/asset paths that didn't
exist in examples/, so the README and SKILL.md quickstart commands
failed. Add schema_version 0.2, update fields, and ship the supporting
article.md, caption.md, cover.png, img-01.png, img-02.png so
`mmp validate examples/longform.yaml` passes out of the box.

* docs: add safety-policy.md (referenced by SKILL.md and README.md)

SKILL.md and README.md link to docs/safety-policy.md but the file did
not exist, leaving the safety contract dangling. Author a v0.2-aware
policy synthesized from the spec and references/publishing-policy.md so
both entry points resolve correctly.

* fix(cli): inline body when copying manifest into run dir for self-containment

Per spec section 3.5(6) the run dir must be self-contained — but the
publish command copied the source manifest byte-for-byte, preserving
relative `body: ./article.md` references that point outside the run dir.
Resume and forensics broke if the source moved or was deleted.

Re-emit the manifest with the already-loaded body inlined so the run dir
stands on its own, mirroring what the lock JSON already does. Add a
regression test that asserts the AI Agent body content lives directly in
the run-dir manifest.yaml.

* feat(core): add settings.toml read/write

* feat(wizard): add loader + stage placeholders

* feat(wizard): build_context for Claude consumption

* feat(wizard): add stage 1 source_extraction prompt

* feat(wizard): add stage 2 target_selection prompt

* feat(wizard): add stage 3 manifest_assembly prompt

* feat(wizard): add credential_setup prompt

* feat(wizard): commit_manifest validates and writes to run dir

* feat(cli): wire mmp wizard --dump-context and --commit

* feat(cli): improve setup prompts; reference credential_setup.md

* docs(skill): add wizard mode and public-publish gate

* docs(handoff): note Plan 2 progress

* chore(plan-2): final cleanup

* feat(wizard): per-account credential status; group accounts by provider

* feat(xiaohongshu): scaffold provider yaml + rules

* feat(xiaohongshu): implement provider with local draft via draft.sh

* feat(wechat_image): scaffold provider yaml + rules; move calibration notes

* feat(wechat_image): implement provider with browser-flow guide draft

* feat(x_article): implement provider as payload-only stub

* feat(substack): implement provider as payload-only stub

* test(integration): image-post dry-run covers xhs + wechat-image

* test(integration): longform multi-target dry-run e2e

* refactor: deprecate v0.1 scripts as thin shims; move wechat notes

* docs(skill): mark all 5 providers available; smoke covers all of them

* docs(handoff): note Plan 3 completion

* chore(plan-3): final cleanup

* fix(providers): use mode_actual=stub for connector-not-implemented draft fallback

* docs: add architecture.md user-facing overview

* docs: add provider authoring guide

* docs: add credentials & vault user guide

* docs: add manual-verification checklist

* docs: archive legacy references; drop superseded files

* feat: add Claude Code plugin manifest

* docs(skill): final v0.2.0 polish; remove plan-N markers

* docs: rewrite README for v0.2 install + usage

* chore: bump 0.2.0 + CHANGELOG

* ci: GitHub Actions matrix (ubuntu + macos × py3.10/3.11/3.12)

* docs(handoff): note Plan 4 completion + v0.3 roadmap

* fix(packaging): add tomli py3.10 dep + mmp console script entry point

* fix(cli): capability gate at validate/publish; reject mode not in provider.capabilities

* chore(deps): drop unused requests; wechat_api uses stdlib urllib

* feat(examples): add multi-target longform-multi.yaml (3 platforms)

* docs(provider-contract): align trust model with v0.2 behavior; defer prompt to v0.3

* docs(readme): note plugin marketplace submission is pending

* fix(examples): shorten image-post.yaml title to ≤20 chars (xhs limit)

Previous title "v0.2 image-post example" was 23 chars; xiaohongshu
TITLE_TOO_LONG rule rejects validate. Replaced with "v0.2 图文示例" (8
chars). Now `mmp validate examples/image-post.yaml` passes.

Found during dry-run acceptance testing.

* fix(manifest): resolve assets.cover/images/video relative paths to absolute

Caught during real-account verification of wechat-article: provider.execute
was getting `Path("./cover.png")` and failing FileNotFoundError because CWD
was the repo root, not the manifest's directory.

`_resolve_asset_path` mirrors `_resolve_inline_or_path` semantics:
- `./` and `../` prefixes resolve relative to manifest dir
- Absolute paths pass through
- `http://` URLs pass through (future extension hook)
- None passes through

Also replaces examples/cover.png with a real 900×500 PNG. The previous 1x1
placeholder PNG (81 bytes) tripped WeChat's `errcode: 40113 unsupported
file type` on `material/add_material?type=thumb`.

Verified end-to-end: dry-run + real WeChat OA draft API now both green.
First successful real `mode_actual: draft-platform` with a valid media_id.

* fix(xiaohongshu): align with real draft.sh contract; fix empty required_keys path

Caught during real-account verification of xhs local-draft. v0.2 implementation
diverged from the actual xiaohongshu skill's draft.sh in three ways:

1. Path discovery — searched ~/.openclaw/skills/ but real install is at
   ~/.openclaw/workspace/skills/. Added that path + XHS_DRAFT_SH env
   override for arbitrary script locations.

2. Invocation contract — passed `--payload <file> --cookie <file>` flags,
   but draft.sh expects a single positional JSON string and does NOT take
   a cookie (local draft writes to ~/.xiaohongshu/drafts/, no API call).
   Now build the JSON inline and pass as $1.

3. Output parsing — draft.sh emits human-readable stdout starting with
   "✓ 已创建本地草稿: <abs path>", not JSON. Parse the path with regex
   and derive draft_id from the basename.

4. Field mapping — draft.sh wants {title, content, images, tags}; mmp's
   payload.json uses {title, caption, ...}. Added _build_xhs_payload to
   reshape; payload.json on disk keeps caption (mmp-canonical) untouched.

Side fix in scripts/mmp.py cmd_publish: when a provider's
required_credentials is [] (e.g. xhs local-draft needs no creds), skip
the vault lookup entirely. Previously `store.get(..., required_keys=[])`
treated [] as None-ish and fell through to "vault has nothing" → raised
MissingCredentialError(['*']).

End-to-end verified: real local xhs draft created at
~/.xiaohongshu/drafts/<ts>-<slug>.json with absolute image paths.

* docs: mark v0.2 ship checklist verified; document 7 real-account bugs found

After running the full manual-verification checklist for wechat-article
(real WeChat OA API draft) and xiaohongshu (real local draft via skill),
record:
- 4 of 6 verification items passed
- 7 integration bugs found and fixed (see HANDOFF "Discovered during
  real-account verification")
- 1 operational pain (split-routing IP whitelist) → v0.3 first task is
  WeChat API proxy support

* chore: ruff format + add format check to make lint

CI caught 4 files needing format that local `make test` missed because
the Makefile only ran `ruff check .`, not `ruff format --check .`.
Files reformatted: core/cli.py, providers/xiaohongshu/provider.py,
providers/xiaohongshu/tests/test_provider.py, scripts/mmp.py.

Also adds `ruff format --check .` to the Makefile lint target so this
mismatch doesn't recur.

* fix(mypy): silence tomli import-not-found on py3.11+ (cond dep)

* fix(mypy): also silence unused-ignore so local py3.10 (with tomli) is happy

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant