diff --git a/.agents/skills/using-superpowers/SKILL.md b/.agents/skills/using-superpowers/SKILL.md
new file mode 100644
index 000000000..d81353529
--- /dev/null
+++ b/.agents/skills/using-superpowers/SKILL.md
@@ -0,0 +1,115 @@
+---
+name: using-superpowers
+description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
+---
+
+
+If you were dispatched as a subagent to execute a specific task, skip this skill.
+
+
+
+If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
+
+IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
+
+This is not negotiable. This is not optional. You cannot rationalize your way out of this.
+
+
+## Instruction Priority
+
+Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:
+
+1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
+2. **Superpowers skills** — override default system behavior where they conflict
+3. **Default system prompt** — lowest priority
+
+If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.
+
+## How to Access Skills
+
+**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
+
+**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.
+
+**In other environments:** Check your platform's documentation for how skills are loaded.
+
+## Platform Adaptation
+
+Skills use Claude Code tool names. Non-CC platforms: see `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
+
+# Using Skills
+
+## The Rule
+
+**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
+
+```dot
+digraph skill_flow {
+ "User message received" [shape=doublecircle];
+ "About to EnterPlanMode?" [shape=doublecircle];
+ "Already brainstormed?" [shape=diamond];
+ "Invoke brainstorming skill" [shape=box];
+ "Might any skill apply?" [shape=diamond];
+ "Invoke Skill tool" [shape=box];
+ "Announce: 'Using [skill] to [purpose]'" [shape=box];
+ "Has checklist?" [shape=diamond];
+ "Create TodoWrite todo per item" [shape=box];
+ "Follow skill exactly" [shape=box];
+ "Respond (including clarifications)" [shape=doublecircle];
+
+ "About to EnterPlanMode?" -> "Already brainstormed?";
+ "Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
+ "Already brainstormed?" -> "Might any skill apply?" [label="yes"];
+ "Invoke brainstorming skill" -> "Might any skill apply?";
+
+ "User message received" -> "Might any skill apply?";
+ "Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
+ "Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
+ "Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
+ "Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
+ "Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
+ "Has checklist?" -> "Follow skill exactly" [label="no"];
+ "Create TodoWrite todo per item" -> "Follow skill exactly";
+}
+```
+
+## Red Flags
+
+These thoughts mean STOP—you're rationalizing:
+
+| Thought | Reality |
+|---------|---------|
+| "This is just a simple question" | Questions are tasks. Check for skills. |
+| "I need more context first" | Skill check comes BEFORE clarifying questions. |
+| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
+| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
+| "Let me gather information first" | Skills tell you HOW to gather information. |
+| "This doesn't need a formal skill" | If a skill exists, use it. |
+| "I remember this skill" | Skills evolve. Read current version. |
+| "This doesn't count as a task" | Action = task. Check for skills. |
+| "The skill is overkill" | Simple things become complex. Use it. |
+| "I'll just do this one thing first" | Check BEFORE doing anything. |
+| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
+| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
+
+## Skill Priority
+
+When multiple skills could apply, use this order:
+
+1. **Process skills first** (brainstorming, debugging) - these determine HOW to approach the task
+2. **Implementation skills second** (frontend-design, mcp-builder) - these guide execution
+
+"Let's build X" → brainstorming first, then implementation skills.
+"Fix this bug" → debugging first, then domain-specific skills.
+
+## Skill Types
+
+**Rigid** (TDD, debugging): Follow exactly. Don't adapt away discipline.
+
+**Flexible** (patterns): Adapt principles to context.
+
+The skill itself tells you which.
+
+## User Instructions
+
+Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.
diff --git a/.agents/skills/using-superpowers/references/codex-tools.md b/.agents/skills/using-superpowers/references/codex-tools.md
new file mode 100644
index 000000000..539b2b1c7
--- /dev/null
+++ b/.agents/skills/using-superpowers/references/codex-tools.md
@@ -0,0 +1,100 @@
+# Codex Tool Mapping
+
+Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
+
+| Skill references | Codex equivalent |
+|-----------------|------------------|
+| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
+| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
+| Task returns result | `wait` |
+| Task completes automatically | `close_agent` to free slot |
+| `TodoWrite` (task tracking) | `update_plan` |
+| `Skill` tool (invoke a skill) | Skills load natively — just follow the instructions |
+| `Read`, `Write`, `Edit` (files) | Use your native file tools |
+| `Bash` (run commands) | Use your native shell tools |
+
+## Subagent dispatch requires multi-agent support
+
+Add to your Codex config (`~/.codex/config.toml`):
+
+```toml
+[features]
+multi_agent = true
+```
+
+This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
+
+## Named agent dispatch
+
+Claude Code skills reference named agent types like `superpowers:code-reviewer`.
+Codex does not have a named agent registry — `spawn_agent` creates generic agents
+from built-in roles (`default`, `explorer`, `worker`).
+
+When a skill says to dispatch a named agent type:
+
+1. Find the agent's prompt file (e.g., `agents/code-reviewer.md` or the skill's
+ local prompt template like `code-quality-reviewer-prompt.md`)
+2. Read the prompt content
+3. Fill any template placeholders (`{BASE_SHA}`, `{WHAT_WAS_IMPLEMENTED}`, etc.)
+4. Spawn a `worker` agent with the filled content as the `message`
+
+| Skill instruction | Codex equivalent |
+|-------------------|------------------|
+| `Task tool (superpowers:code-reviewer)` | `spawn_agent(agent_type="worker", message=...)` with `code-reviewer.md` content |
+| `Task tool (general-purpose)` with inline prompt | `spawn_agent(message=...)` with the same prompt |
+
+### Message framing
+
+The `message` parameter is user-level input, not a system prompt. Structure it
+for maximum instruction adherence:
+
+```
+Your task is to perform the following. Follow the instructions below exactly.
+
+
+[filled prompt content from the agent's .md file]
+
+
+Execute this now. Output ONLY the structured response following the format
+specified in the instructions above.
+```
+
+- Use task-delegation framing ("Your task is...") rather than persona framing ("You are...")
+- Wrap instructions in XML tags — the model treats tagged blocks as authoritative
+- End with an explicit execution directive to prevent summarization of the instructions
+
+### When this workaround can be removed
+
+This approach compensates for Codex's plugin system not yet supporting an `agents`
+field in `plugin.json`. When `RawPluginManifest` gains an `agents` field, the
+plugin can symlink to `agents/` (mirroring the existing `skills/` symlink) and
+skills can dispatch named agent types directly.
+
+## Environment Detection
+
+Skills that create worktrees or finish branches should detect their
+environment with read-only git commands before proceeding:
+
+```bash
+GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
+GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
+BRANCH=$(git branch --show-current)
+```
+
+- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
+- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
+
+See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
+Step 1 for how each skill uses these signals.
+
+## Codex App Finishing
+
+When the sandbox blocks branch/push operations (detached HEAD in an
+externally managed worktree), the agent commits all work and informs
+the user to use the App's native controls:
+
+- **"Create branch"** — names the branch, then commit/push/PR via App UI
+- **"Hand off to local"** — transfers work to the user's local checkout
+
+The agent can still run tests, stage files, and output suggested branch
+names, commit messages, and PR descriptions for the user to copy.
diff --git a/.agents/skills/using-superpowers/references/gemini-tools.md b/.agents/skills/using-superpowers/references/gemini-tools.md
new file mode 100644
index 000000000..f8698033b
--- /dev/null
+++ b/.agents/skills/using-superpowers/references/gemini-tools.md
@@ -0,0 +1,33 @@
+# Gemini CLI Tool Mapping
+
+Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
+
+| Skill references | Gemini CLI equivalent |
+|-----------------|----------------------|
+| `Read` (file reading) | `read_file` |
+| `Write` (file creation) | `write_file` |
+| `Edit` (file editing) | `replace` |
+| `Bash` (run commands) | `run_shell_command` |
+| `Grep` (search file content) | `grep_search` |
+| `Glob` (search files by name) | `glob` |
+| `TodoWrite` (task tracking) | `write_todos` |
+| `Skill` tool (invoke a skill) | `activate_skill` |
+| `WebSearch` | `google_web_search` |
+| `WebFetch` | `web_fetch` |
+| `Task` tool (dispatch subagent) | No equivalent — Gemini CLI does not support subagents |
+
+## No subagent support
+
+Gemini CLI has no equivalent to Claude Code's `Task` tool. Skills that rely on subagent dispatch (`subagent-driven-development`, `dispatching-parallel-agents`) will fall back to single-session execution via `executing-plans`.
+
+## Additional Gemini CLI tools
+
+These tools are available in Gemini CLI but have no Claude Code equivalent:
+
+| Tool | Purpose |
+|------|---------|
+| `list_directory` | List files and subdirectories |
+| `save_memory` | Persist facts to GEMINI.md across sessions |
+| `ask_user` | Request structured input from the user |
+| `tracker_create_task` | Rich task management (create, update, list, visualize) |
+| `enter_plan_mode` / `exit_plan_mode` | Switch to read-only research mode before making changes |
diff --git a/.cursor/skills/using-superpowers/SKILL.md b/.cursor/skills/using-superpowers/SKILL.md
new file mode 100644
index 000000000..d81353529
--- /dev/null
+++ b/.cursor/skills/using-superpowers/SKILL.md
@@ -0,0 +1,115 @@
+---
+name: using-superpowers
+description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
+---
+
+
+If you were dispatched as a subagent to execute a specific task, skip this skill.
+
+
+
+If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
+
+IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
+
+This is not negotiable. This is not optional. You cannot rationalize your way out of this.
+
+
+## Instruction Priority
+
+Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:
+
+1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
+2. **Superpowers skills** — override default system behavior where they conflict
+3. **Default system prompt** — lowest priority
+
+If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.
+
+## How to Access Skills
+
+**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
+
+**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.
+
+**In other environments:** Check your platform's documentation for how skills are loaded.
+
+## Platform Adaptation
+
+Skills use Claude Code tool names. Non-CC platforms: see `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
+
+# Using Skills
+
+## The Rule
+
+**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
+
+```dot
+digraph skill_flow {
+ "User message received" [shape=doublecircle];
+ "About to EnterPlanMode?" [shape=doublecircle];
+ "Already brainstormed?" [shape=diamond];
+ "Invoke brainstorming skill" [shape=box];
+ "Might any skill apply?" [shape=diamond];
+ "Invoke Skill tool" [shape=box];
+ "Announce: 'Using [skill] to [purpose]'" [shape=box];
+ "Has checklist?" [shape=diamond];
+ "Create TodoWrite todo per item" [shape=box];
+ "Follow skill exactly" [shape=box];
+ "Respond (including clarifications)" [shape=doublecircle];
+
+ "About to EnterPlanMode?" -> "Already brainstormed?";
+ "Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
+ "Already brainstormed?" -> "Might any skill apply?" [label="yes"];
+ "Invoke brainstorming skill" -> "Might any skill apply?";
+
+ "User message received" -> "Might any skill apply?";
+ "Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
+ "Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
+ "Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
+ "Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
+ "Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
+ "Has checklist?" -> "Follow skill exactly" [label="no"];
+ "Create TodoWrite todo per item" -> "Follow skill exactly";
+}
+```
+
+## Red Flags
+
+These thoughts mean STOP—you're rationalizing:
+
+| Thought | Reality |
+|---------|---------|
+| "This is just a simple question" | Questions are tasks. Check for skills. |
+| "I need more context first" | Skill check comes BEFORE clarifying questions. |
+| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
+| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
+| "Let me gather information first" | Skills tell you HOW to gather information. |
+| "This doesn't need a formal skill" | If a skill exists, use it. |
+| "I remember this skill" | Skills evolve. Read current version. |
+| "This doesn't count as a task" | Action = task. Check for skills. |
+| "The skill is overkill" | Simple things become complex. Use it. |
+| "I'll just do this one thing first" | Check BEFORE doing anything. |
+| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
+| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
+
+## Skill Priority
+
+When multiple skills could apply, use this order:
+
+1. **Process skills first** (brainstorming, debugging) - these determine HOW to approach the task
+2. **Implementation skills second** (frontend-design, mcp-builder) - these guide execution
+
+"Let's build X" → brainstorming first, then implementation skills.
+"Fix this bug" → debugging first, then domain-specific skills.
+
+## Skill Types
+
+**Rigid** (TDD, debugging): Follow exactly. Don't adapt away discipline.
+
+**Flexible** (patterns): Adapt principles to context.
+
+The skill itself tells you which.
+
+## User Instructions
+
+Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.
diff --git a/.cursor/skills/using-superpowers/references/codex-tools.md b/.cursor/skills/using-superpowers/references/codex-tools.md
new file mode 100644
index 000000000..539b2b1c7
--- /dev/null
+++ b/.cursor/skills/using-superpowers/references/codex-tools.md
@@ -0,0 +1,100 @@
+# Codex Tool Mapping
+
+Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
+
+| Skill references | Codex equivalent |
+|-----------------|------------------|
+| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
+| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
+| Task returns result | `wait` |
+| Task completes automatically | `close_agent` to free slot |
+| `TodoWrite` (task tracking) | `update_plan` |
+| `Skill` tool (invoke a skill) | Skills load natively — just follow the instructions |
+| `Read`, `Write`, `Edit` (files) | Use your native file tools |
+| `Bash` (run commands) | Use your native shell tools |
+
+## Subagent dispatch requires multi-agent support
+
+Add to your Codex config (`~/.codex/config.toml`):
+
+```toml
+[features]
+multi_agent = true
+```
+
+This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
+
+## Named agent dispatch
+
+Claude Code skills reference named agent types like `superpowers:code-reviewer`.
+Codex does not have a named agent registry — `spawn_agent` creates generic agents
+from built-in roles (`default`, `explorer`, `worker`).
+
+When a skill says to dispatch a named agent type:
+
+1. Find the agent's prompt file (e.g., `agents/code-reviewer.md` or the skill's
+ local prompt template like `code-quality-reviewer-prompt.md`)
+2. Read the prompt content
+3. Fill any template placeholders (`{BASE_SHA}`, `{WHAT_WAS_IMPLEMENTED}`, etc.)
+4. Spawn a `worker` agent with the filled content as the `message`
+
+| Skill instruction | Codex equivalent |
+|-------------------|------------------|
+| `Task tool (superpowers:code-reviewer)` | `spawn_agent(agent_type="worker", message=...)` with `code-reviewer.md` content |
+| `Task tool (general-purpose)` with inline prompt | `spawn_agent(message=...)` with the same prompt |
+
+### Message framing
+
+The `message` parameter is user-level input, not a system prompt. Structure it
+for maximum instruction adherence:
+
+```
+Your task is to perform the following. Follow the instructions below exactly.
+
+
+[filled prompt content from the agent's .md file]
+
+
+Execute this now. Output ONLY the structured response following the format
+specified in the instructions above.
+```
+
+- Use task-delegation framing ("Your task is...") rather than persona framing ("You are...")
+- Wrap instructions in XML tags — the model treats tagged blocks as authoritative
+- End with an explicit execution directive to prevent summarization of the instructions
+
+### When this workaround can be removed
+
+This approach compensates for Codex's plugin system not yet supporting an `agents`
+field in `plugin.json`. When `RawPluginManifest` gains an `agents` field, the
+plugin can symlink to `agents/` (mirroring the existing `skills/` symlink) and
+skills can dispatch named agent types directly.
+
+## Environment Detection
+
+Skills that create worktrees or finish branches should detect their
+environment with read-only git commands before proceeding:
+
+```bash
+GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
+GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
+BRANCH=$(git branch --show-current)
+```
+
+- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
+- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
+
+See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
+Step 1 for how each skill uses these signals.
+
+## Codex App Finishing
+
+When the sandbox blocks branch/push operations (detached HEAD in an
+externally managed worktree), the agent commits all work and informs
+the user to use the App's native controls:
+
+- **"Create branch"** — names the branch, then commit/push/PR via App UI
+- **"Hand off to local"** — transfers work to the user's local checkout
+
+The agent can still run tests, stage files, and output suggested branch
+names, commit messages, and PR descriptions for the user to copy.
diff --git a/.cursor/skills/using-superpowers/references/gemini-tools.md b/.cursor/skills/using-superpowers/references/gemini-tools.md
new file mode 100644
index 000000000..f8698033b
--- /dev/null
+++ b/.cursor/skills/using-superpowers/references/gemini-tools.md
@@ -0,0 +1,33 @@
+# Gemini CLI Tool Mapping
+
+Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
+
+| Skill references | Gemini CLI equivalent |
+|-----------------|----------------------|
+| `Read` (file reading) | `read_file` |
+| `Write` (file creation) | `write_file` |
+| `Edit` (file editing) | `replace` |
+| `Bash` (run commands) | `run_shell_command` |
+| `Grep` (search file content) | `grep_search` |
+| `Glob` (search files by name) | `glob` |
+| `TodoWrite` (task tracking) | `write_todos` |
+| `Skill` tool (invoke a skill) | `activate_skill` |
+| `WebSearch` | `google_web_search` |
+| `WebFetch` | `web_fetch` |
+| `Task` tool (dispatch subagent) | No equivalent — Gemini CLI does not support subagents |
+
+## No subagent support
+
+Gemini CLI has no equivalent to Claude Code's `Task` tool. Skills that rely on subagent dispatch (`subagent-driven-development`, `dispatching-parallel-agents`) will fall back to single-session execution via `executing-plans`.
+
+## Additional Gemini CLI tools
+
+These tools are available in Gemini CLI but have no Claude Code equivalent:
+
+| Tool | Purpose |
+|------|---------|
+| `list_directory` | List files and subdirectories |
+| `save_memory` | Persist facts to GEMINI.md across sessions |
+| `ask_user` | Request structured input from the user |
+| `tracker_create_task` | Rich task management (create, update, list, visualize) |
+| `enter_plan_mode` / `exit_plan_mode` | Switch to read-only research mode before making changes |
diff --git a/.trae/skills/task-plugin-dev/SKILL.md b/.trae/skills/task-plugin-dev/SKILL.md
index b4f1dd021..8f145089c 100644
--- a/.trae/skills/task-plugin-dev/SKILL.md
+++ b/.trae/skills/task-plugin-dev/SKILL.md
@@ -89,16 +89,55 @@ certDomains!: string[];
accessId!: string;
```
-### 4. 实现插件方法
+### 4. 动态显隐配置(mergeScript)
-#### 4.1 插件实例化时执行的方法
+使用 `mergeScript` 可以实现根据其他输入值动态控制当前输入项的显隐状态。
+
+```typescript
+@TaskInput({
+ title: '匹配模式',
+ component: {
+ name: 'select',
+ options: [
+ { label: '手动选择', value: 'manual' },
+ { label: '根据证书匹配', value: 'auto' },
+ ],
+ },
+ default: 'manual',
+})
+domainMatchMode!: 'manual' | 'auto';
+
+@TaskInput(
+ createRemoteSelectInputDefine({
+ title: 'DCDN加速域名',
+ helper: '你在阿里云上配置的DCDN加速域名',
+ action: DeployCertToAliyunDCDN.prototype.onGetDomainList.name,
+ watches: ['certDomains', 'accessId'],
+ required: true,
+ mergeScript: `
+ return {
+ show: ctx.compute(({form})=>{
+ return domainMatchMode === "manual"
+ })
+ }
+ `,
+ })
+)
+domainName!: string | string[];
+```
+
+`mergeScript` 中的 `ctx.compute` 函数接收一个回调函数,通过 `form` 参数可以访问表单中的其他字段值。
+
+### 5. 实现插件方法
+
+#### 5.1 插件实例化时执行的方法
```typescript
// 插件实例化时执行的方法
async onInstance() {}
```
-#### 4.2 插件执行方法
+#### 5.2 插件执行方法
```typescript
// 插件执行方法
@@ -130,7 +169,9 @@ async execute(): Promise {
}
```
-#### 4.3 后端获取选项方法
+#### 5.3 后端获取选项方法
+
+使用 `createRemoteSelectInputDefine` 创建远程选择输入项,`action` 指向的方法接收 `PageSearch` 参数并返回 `{ list, total }` 格式。
```typescript
@TaskInput(
@@ -145,8 +186,8 @@ async execute(): Promise {
)
siteName!: string | string[];
-// 从后端获取选项的方法
-async onGetSiteList(req: PageSearch) {
+// 从后端获取选项的方法,接收PageSearch参数
+async onGetSiteList(data: PageSearch) {
if (!this.accessId) {
throw new Error('请选择Access授权');
}
@@ -154,7 +195,7 @@ async onGetSiteList(req: PageSearch) {
// @ts-ignore
const access = await this.getAccess(this.accessId);
- // const siteRes = await access.GetDomainList(req);
+ // const siteRes = await access.GetDomainList(data);
// 以下是模拟数据
const siteRes = [
{ id: 1, siteName: 'site1.com' },
@@ -169,8 +210,12 @@ async onGetSiteList(req: PageSearch) {
domain: item.siteName,
};
});
- // 将站点域名名称根据证书域名进行匹配分组,分成匹配的和不匹配的两组选项,返回给前端,供用户选择
- return optionsUtils.buildGroupOptions(options, this.certDomains);
+
+ // 返回{list, total}格式
+ return {
+ list: optionsUtils.buildGroupOptions(options, this.certDomains),
+ total: siteRes.length,
+ };
}
```
@@ -383,7 +428,10 @@ export class DemoTest extends AbstractTaskPlugin {
};
});
//将站点域名名称根据证书域名进行匹配分组,分成匹配的和不匹配的两组选项,返回给前端,供用户选择
- return optionsUtils.buildGroupOptions(options, this.certDomains);
+ return {
+ list: optionsUtils.buildGroupOptions(options, this.certDomains),
+ total: siteRes.length,
+ };
}
}
```
\ No newline at end of file
diff --git a/.trae/skills/using-superpowers/SKILL.md b/.trae/skills/using-superpowers/SKILL.md
new file mode 100644
index 000000000..d81353529
--- /dev/null
+++ b/.trae/skills/using-superpowers/SKILL.md
@@ -0,0 +1,115 @@
+---
+name: using-superpowers
+description: Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions
+---
+
+
+If you were dispatched as a subagent to execute a specific task, skip this skill.
+
+
+
+If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.
+
+IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
+
+This is not negotiable. This is not optional. You cannot rationalize your way out of this.
+
+
+## Instruction Priority
+
+Superpowers skills override default system prompt behavior, but **user instructions always take precedence**:
+
+1. **User's explicit instructions** (CLAUDE.md, GEMINI.md, AGENTS.md, direct requests) — highest priority
+2. **Superpowers skills** — override default system behavior where they conflict
+3. **Default system prompt** — lowest priority
+
+If CLAUDE.md, GEMINI.md, or AGENTS.md says "don't use TDD" and a skill says "always use TDD," follow the user's instructions. The user is in control.
+
+## How to Access Skills
+
+**In Claude Code:** Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.
+
+**In Gemini CLI:** Skills activate via the `activate_skill` tool. Gemini loads skill metadata at session start and activates the full content on demand.
+
+**In other environments:** Check your platform's documentation for how skills are loaded.
+
+## Platform Adaptation
+
+Skills use Claude Code tool names. Non-CC platforms: see `references/codex-tools.md` (Codex) for tool equivalents. Gemini CLI users get the tool mapping loaded automatically via GEMINI.md.
+
+# Using Skills
+
+## The Rule
+
+**Invoke relevant or requested skills BEFORE any response or action.** Even a 1% chance a skill might apply means that you should invoke the skill to check. If an invoked skill turns out to be wrong for the situation, you don't need to use it.
+
+```dot
+digraph skill_flow {
+ "User message received" [shape=doublecircle];
+ "About to EnterPlanMode?" [shape=doublecircle];
+ "Already brainstormed?" [shape=diamond];
+ "Invoke brainstorming skill" [shape=box];
+ "Might any skill apply?" [shape=diamond];
+ "Invoke Skill tool" [shape=box];
+ "Announce: 'Using [skill] to [purpose]'" [shape=box];
+ "Has checklist?" [shape=diamond];
+ "Create TodoWrite todo per item" [shape=box];
+ "Follow skill exactly" [shape=box];
+ "Respond (including clarifications)" [shape=doublecircle];
+
+ "About to EnterPlanMode?" -> "Already brainstormed?";
+ "Already brainstormed?" -> "Invoke brainstorming skill" [label="no"];
+ "Already brainstormed?" -> "Might any skill apply?" [label="yes"];
+ "Invoke brainstorming skill" -> "Might any skill apply?";
+
+ "User message received" -> "Might any skill apply?";
+ "Might any skill apply?" -> "Invoke Skill tool" [label="yes, even 1%"];
+ "Might any skill apply?" -> "Respond (including clarifications)" [label="definitely not"];
+ "Invoke Skill tool" -> "Announce: 'Using [skill] to [purpose]'";
+ "Announce: 'Using [skill] to [purpose]'" -> "Has checklist?";
+ "Has checklist?" -> "Create TodoWrite todo per item" [label="yes"];
+ "Has checklist?" -> "Follow skill exactly" [label="no"];
+ "Create TodoWrite todo per item" -> "Follow skill exactly";
+}
+```
+
+## Red Flags
+
+These thoughts mean STOP—you're rationalizing:
+
+| Thought | Reality |
+|---------|---------|
+| "This is just a simple question" | Questions are tasks. Check for skills. |
+| "I need more context first" | Skill check comes BEFORE clarifying questions. |
+| "Let me explore the codebase first" | Skills tell you HOW to explore. Check first. |
+| "I can check git/files quickly" | Files lack conversation context. Check for skills. |
+| "Let me gather information first" | Skills tell you HOW to gather information. |
+| "This doesn't need a formal skill" | If a skill exists, use it. |
+| "I remember this skill" | Skills evolve. Read current version. |
+| "This doesn't count as a task" | Action = task. Check for skills. |
+| "The skill is overkill" | Simple things become complex. Use it. |
+| "I'll just do this one thing first" | Check BEFORE doing anything. |
+| "This feels productive" | Undisciplined action wastes time. Skills prevent this. |
+| "I know what that means" | Knowing the concept ≠ using the skill. Invoke it. |
+
+## Skill Priority
+
+When multiple skills could apply, use this order:
+
+1. **Process skills first** (brainstorming, debugging) - these determine HOW to approach the task
+2. **Implementation skills second** (frontend-design, mcp-builder) - these guide execution
+
+"Let's build X" → brainstorming first, then implementation skills.
+"Fix this bug" → debugging first, then domain-specific skills.
+
+## Skill Types
+
+**Rigid** (TDD, debugging): Follow exactly. Don't adapt away discipline.
+
+**Flexible** (patterns): Adapt principles to context.
+
+The skill itself tells you which.
+
+## User Instructions
+
+Instructions say WHAT, not HOW. "Add X" or "Fix Y" doesn't mean skip workflows.
diff --git a/.trae/skills/using-superpowers/references/codex-tools.md b/.trae/skills/using-superpowers/references/codex-tools.md
new file mode 100644
index 000000000..539b2b1c7
--- /dev/null
+++ b/.trae/skills/using-superpowers/references/codex-tools.md
@@ -0,0 +1,100 @@
+# Codex Tool Mapping
+
+Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
+
+| Skill references | Codex equivalent |
+|-----------------|------------------|
+| `Task` tool (dispatch subagent) | `spawn_agent` (see [Named agent dispatch](#named-agent-dispatch)) |
+| Multiple `Task` calls (parallel) | Multiple `spawn_agent` calls |
+| Task returns result | `wait` |
+| Task completes automatically | `close_agent` to free slot |
+| `TodoWrite` (task tracking) | `update_plan` |
+| `Skill` tool (invoke a skill) | Skills load natively — just follow the instructions |
+| `Read`, `Write`, `Edit` (files) | Use your native file tools |
+| `Bash` (run commands) | Use your native shell tools |
+
+## Subagent dispatch requires multi-agent support
+
+Add to your Codex config (`~/.codex/config.toml`):
+
+```toml
+[features]
+multi_agent = true
+```
+
+This enables `spawn_agent`, `wait`, and `close_agent` for skills like `dispatching-parallel-agents` and `subagent-driven-development`.
+
+## Named agent dispatch
+
+Claude Code skills reference named agent types like `superpowers:code-reviewer`.
+Codex does not have a named agent registry — `spawn_agent` creates generic agents
+from built-in roles (`default`, `explorer`, `worker`).
+
+When a skill says to dispatch a named agent type:
+
+1. Find the agent's prompt file (e.g., `agents/code-reviewer.md` or the skill's
+ local prompt template like `code-quality-reviewer-prompt.md`)
+2. Read the prompt content
+3. Fill any template placeholders (`{BASE_SHA}`, `{WHAT_WAS_IMPLEMENTED}`, etc.)
+4. Spawn a `worker` agent with the filled content as the `message`
+
+| Skill instruction | Codex equivalent |
+|-------------------|------------------|
+| `Task tool (superpowers:code-reviewer)` | `spawn_agent(agent_type="worker", message=...)` with `code-reviewer.md` content |
+| `Task tool (general-purpose)` with inline prompt | `spawn_agent(message=...)` with the same prompt |
+
+### Message framing
+
+The `message` parameter is user-level input, not a system prompt. Structure it
+for maximum instruction adherence:
+
+```
+Your task is to perform the following. Follow the instructions below exactly.
+
+
+[filled prompt content from the agent's .md file]
+
+
+Execute this now. Output ONLY the structured response following the format
+specified in the instructions above.
+```
+
+- Use task-delegation framing ("Your task is...") rather than persona framing ("You are...")
+- Wrap instructions in XML tags — the model treats tagged blocks as authoritative
+- End with an explicit execution directive to prevent summarization of the instructions
+
+### When this workaround can be removed
+
+This approach compensates for Codex's plugin system not yet supporting an `agents`
+field in `plugin.json`. When `RawPluginManifest` gains an `agents` field, the
+plugin can symlink to `agents/` (mirroring the existing `skills/` symlink) and
+skills can dispatch named agent types directly.
+
+## Environment Detection
+
+Skills that create worktrees or finish branches should detect their
+environment with read-only git commands before proceeding:
+
+```bash
+GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P)
+GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P)
+BRANCH=$(git branch --show-current)
+```
+
+- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation)
+- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox)
+
+See `using-git-worktrees` Step 0 and `finishing-a-development-branch`
+Step 1 for how each skill uses these signals.
+
+## Codex App Finishing
+
+When the sandbox blocks branch/push operations (detached HEAD in an
+externally managed worktree), the agent commits all work and informs
+the user to use the App's native controls:
+
+- **"Create branch"** — names the branch, then commit/push/PR via App UI
+- **"Hand off to local"** — transfers work to the user's local checkout
+
+The agent can still run tests, stage files, and output suggested branch
+names, commit messages, and PR descriptions for the user to copy.
diff --git a/.trae/skills/using-superpowers/references/gemini-tools.md b/.trae/skills/using-superpowers/references/gemini-tools.md
new file mode 100644
index 000000000..f8698033b
--- /dev/null
+++ b/.trae/skills/using-superpowers/references/gemini-tools.md
@@ -0,0 +1,33 @@
+# Gemini CLI Tool Mapping
+
+Skills use Claude Code tool names. When you encounter these in a skill, use your platform equivalent:
+
+| Skill references | Gemini CLI equivalent |
+|-----------------|----------------------|
+| `Read` (file reading) | `read_file` |
+| `Write` (file creation) | `write_file` |
+| `Edit` (file editing) | `replace` |
+| `Bash` (run commands) | `run_shell_command` |
+| `Grep` (search file content) | `grep_search` |
+| `Glob` (search files by name) | `glob` |
+| `TodoWrite` (task tracking) | `write_todos` |
+| `Skill` tool (invoke a skill) | `activate_skill` |
+| `WebSearch` | `google_web_search` |
+| `WebFetch` | `web_fetch` |
+| `Task` tool (dispatch subagent) | No equivalent — Gemini CLI does not support subagents |
+
+## No subagent support
+
+Gemini CLI has no equivalent to Claude Code's `Task` tool. Skills that rely on subagent dispatch (`subagent-driven-development`, `dispatching-parallel-agents`) will fall back to single-session execution via `executing-plans`.
+
+## Additional Gemini CLI tools
+
+These tools are available in Gemini CLI but have no Claude Code equivalent:
+
+| Tool | Purpose |
+|------|---------|
+| `list_directory` | List files and subdirectories |
+| `save_memory` | Persist facts to GEMINI.md across sessions |
+| `ask_user` | Request structured input from the user |
+| `tracker_create_task` | Rich task management (create, update, list, visualize) |
+| `enter_plan_mode` / `exit_plan_mode` | Switch to read-only research mode before making changes |
diff --git a/packages/core/pipeline/src/plugin/api.ts b/packages/core/pipeline/src/plugin/api.ts
index d4c837577..323ad82e2 100644
--- a/packages/core/pipeline/src/plugin/api.ts
+++ b/packages/core/pipeline/src/plugin/api.ts
@@ -4,7 +4,7 @@ import { FileStore } from "../core/file-store.js";
import { accessRegistry, IAccessService } from "../access/index.js";
import { ICnameProxyService, IEmailService, IServiceGetter, IUrlService } from "../service/index.js";
import { CancelError, IContext, RunHistory, RunnableCollection } from "../core/index.js";
-import { HttpRequestConfig, ILogger, logger, utils } from "@certd/basic";
+import { HttpRequestConfig, ILogger, logger, optionsUtils, utils } from "@certd/basic";
import { HttpClient } from "@certd/basic";
import dayjs from "dayjs";
import { IPluginConfigService } from "../service/config.js";
@@ -315,6 +315,11 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
getLastOutput(key: string) {
return this.getLastStatus().status?.output?.[key];
}
+
+ getMatchedDomains(domainList: string[], certDomains: string[]): string[] {
+ const { matched } = optionsUtils.groupByDomain(domainList, certDomains);
+ return matched;
+ }
}
export type OutputVO = {
diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts
index c6d71ba06..32376652e 100644
--- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts
+++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts
@@ -589,11 +589,11 @@ export default {
userValidityPeriodHelper: "有效期内用户可正常使用,失效后用户的流水线将被停用",
enableUsernameRegistration: "开启用户名注册",
enableEmailRegistration: "开启邮箱注册",
- proFeature: "专业版功能",
+ proFeature: "Certd专业版功能",
emailServerSetup: "设置邮箱服务器",
enableSmsLoginRegister: "开启手机号登录、注册",
defaultLoginType: "默认登录方式",
- commFeature: "商业版功能",
+ commFeature: "Certd商业版功能",
smsProvider: "短信提供商",
aliyunSms: "阿里云短信",
tencentSms: "腾讯云短信",
diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/vip.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/vip.ts
index 2877bce9f..b82a9e4cc 100644
--- a/packages/ui/certd-client/src/locales/langs/zh-CN/vip.ts
+++ b/packages/ui/certd-client/src/locales/langs/zh-CN/vip.ts
@@ -88,13 +88,13 @@ export default {
activation_code_one_use: "激活码使用过一次之后,不可再次使用,如果要更换站点,请",
bind_account: "绑定账号",
transfer_vip: '然后"转移VIP"即可',
- needVipTip: "此为专业版功能,请先开通专业版",
+ needVipTip: "此为Certd专业版功能,请先开通Certd专业版",
manual_activation: "激活码手动激活",
close: "关闭",
have_activation_code: "已经有激活码了?",
buy: "立即购买",
- already_plus: "已经是专业版了,是否升级为商业版?注意:专业版时长将被覆盖",
- already_comm: "已经是商业版了,不能降级为专业版",
+ already_plus: "已经是Certd专业版了,是否升级为商业版?注意:Certd专业版时长将被覆盖",
+ already_comm: "已经是Certd商业版了,不能降级为专业版",
already_perpetual_plus: "您已经是永久专业版了,无法继续升级",
confirm: "确认",
not_effective: "VIP没有生效/时长未同步?",
diff --git a/packages/ui/certd-client/src/views/sys/account/index.vue b/packages/ui/certd-client/src/views/sys/account/index.vue
index 81c08a199..18a1f9685 100644
--- a/packages/ui/certd-client/src/views/sys/account/index.vue
+++ b/packages/ui/certd-client/src/views/sys/account/index.vue
@@ -80,7 +80,7 @@ onMounted(() => {
await settingStore.doBindUrl();
notification.success({
message: "更新成功",
- description: "专业版/商业版已激活",
+ description: "Certd专业版/商业版已激活",
});
});
});
diff --git a/packages/ui/certd-server/src/controller/user/mine/setting-two-factor-controller.ts b/packages/ui/certd-server/src/controller/user/mine/setting-two-factor-controller.ts
index 9f0e0bef0..8b84483f1 100644
--- a/packages/ui/certd-server/src/controller/user/mine/setting-two-factor-controller.ts
+++ b/packages/ui/certd-server/src/controller/user/mine/setting-two-factor-controller.ts
@@ -31,7 +31,7 @@ export class UserTwoFactorSettingController extends BaseController {
@Post("/save", { description: Constants.per.authOnly, summary: "保存双因子认证设置" })
async save(@Body(ALL) bean: any) {
if (!isPlus()) {
- throw new Error('本功能需要开通专业版')
+ throw new Error('本功能需要开通Certd专业版')
}
const userId = this.getUserId();
const setting = new UserTwoFactorSetting();
@@ -57,7 +57,7 @@ export class UserTwoFactorSettingController extends BaseController {
@Post("/authenticator/save", { description: Constants.per.authOnly, summary: "保存验证器设置" })
async authenticatorSave(@Body(ALL) bean: any) {
if (!isPlus()) {
- throw new Error('本功能需要开通专业版')
+ throw new Error('本功能需要开通Certd专业版')
}
const userId = this.getUserId();
await this.twoFactorService.saveAuthenticator({
diff --git a/packages/ui/certd-server/src/controller/user/mine/user-settings-controller.ts b/packages/ui/certd-server/src/controller/user/mine/user-settings-controller.ts
index 755409b3d..cd4cf11c9 100644
--- a/packages/ui/certd-server/src/controller/user/mine/user-settings-controller.ts
+++ b/packages/ui/certd-server/src/controller/user/mine/user-settings-controller.ts
@@ -81,7 +81,7 @@ export class UserSettingsController extends CrudController
@Post("/grant/save", { description: Constants.per.authOnly, summary: "保存授权设置" })
async grantSettingsSave(@Body(ALL) bean: UserGrantSetting) {
if (!isPlus()) {
- throw new Error('本功能需要开通专业版')
+ throw new Error('本功能需要开通Certd专业版')
}
const userId = this.getUserId();
const setting = new UserGrantSetting();
diff --git a/packages/ui/certd-server/src/modules/login/service/login-service.ts b/packages/ui/certd-server/src/modules/login/service/login-service.ts
index c46472335..30b85c9a8 100644
--- a/packages/ui/certd-server/src/modules/login/service/login-service.ts
+++ b/packages/ui/certd-server/src/modules/login/service/login-service.ts
@@ -180,7 +180,7 @@ export class LoginService {
async loginByTwoFactor(req: { loginId: string; verifyCode: string }) {
//检查是否开启多重认证
if (!isPlus()) {
- throw new Error('本功能需要开通专业版')
+ throw new Error('本功能需要开通Certd专业版')
}
const userId = cache.get(`login_2fa_code:${req.loginId}`)
if (!userId) {
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/notification-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/notification-service.ts
index dd7311be5..aaf1cc51e 100644
--- a/packages/ui/certd-server/src/modules/pipeline/service/notification-service.ts
+++ b/packages/ui/certd-server/src/modules/pipeline/service/notification-service.ts
@@ -83,7 +83,7 @@ export class NotificationService extends BaseService {
const define = this.getDefineByType(type)
//@ts-ignore
if (define.needPlus && !isPlus()) {
- throw new NeedVIPException("此通知类型为专业版功能,请升级到专业版或以上级别");
+ throw new NeedVIPException("此通知类型为Certd专业版功能,请升级到专业版或以上级别");
}
}
diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
index 06df80d7b..8233fb9d9 100644
--- a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
+++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts
@@ -941,7 +941,7 @@ export class PipelineService extends BaseService {
async batchDelete(ids: number[], userId?: number, projectId?: number) {
if (!isPlus()) {
- throw new NeedVIPException("此功能需要升级专业版");
+ throw new NeedVIPException("此功能需要升级Certd专业版");
}
for (const id of ids) {
if (userId && userId > 0) {
@@ -956,7 +956,7 @@ export class PipelineService extends BaseService {
async batchUpdateGroup(ids: number[], groupId: number, userId: any, projectId?: number) {
if (!isPlus()) {
- throw new NeedVIPException("此功能需要升级专业版");
+ throw new NeedVIPException("此功能需要升级Certd专业版");
}
const query: any = {}
if (userId && userId > 0) {
@@ -982,7 +982,7 @@ export class PipelineService extends BaseService {
*/
async batchTransfer(ids: number[], projectId: number) {
if (!isPlus()) {
- throw new NeedVIPException("此功能需要升级专业版");
+ throw new NeedVIPException("此功能需要升级Certd专业版");
}
if (!isEnterprise()) {
throw new Error("当前为非企业模式,不允许转移到其他项目");
@@ -1075,7 +1075,7 @@ export class PipelineService extends BaseService {
async batchUpdateTrigger(ids: number[], trigger: any, userId: any, projectId?: number) {
if (!isPlus()) {
- throw new NeedVIPException("此功能需要升级专业版");
+ throw new NeedVIPException("此功能需要升级Certd专业版");
}
//允许管理员修改,userId=null
const query: any = {}
@@ -1128,7 +1128,7 @@ export class PipelineService extends BaseService {
async batchUpdateNotifications(ids: number[], notification: Notification, userId: any, projectId?: number) {
if (!isPlus()) {
- throw new NeedVIPException("此功能需要升级专业版");
+ throw new NeedVIPException("此功能需要升级Certd专业版");
}
//允许管理员修改,userId=null
const query: any = {}
@@ -1167,7 +1167,7 @@ export class PipelineService extends BaseService {
async batchRerun(ids: number[], force: boolean, userId: any, projectId?: number) {
if (!isPlus()) {
- throw new NeedVIPException("此功能需要升级专业版");
+ throw new NeedVIPException("此功能需要升级Certd专业版");
}
//允许userId为空,为空则为管理员触发
if (ids.length === 0) {
diff --git a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.ts
index 632e3f660..b1732cfd3 100644
--- a/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.ts
+++ b/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-dcdn/index.ts
@@ -1,4 +1,4 @@
-import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
+import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import dayjs from 'dayjs';
import {
createCertDomainGetterInputDefine,
@@ -55,6 +55,19 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
})
certName!: string;
+ @TaskInput({
+ title: '域名匹配模式',
+ helper: '选择域名匹配方式',
+ component: {
+ name: 'select',
+ options: [
+ { label: '手动选择', value: 'manual' },
+ { label: '根据证书匹配', value: 'auto' },
+ ],
+ },
+ default: 'manual',
+ })
+ domainMatchMode!: 'manual' | 'auto';
@TaskInput(
createRemoteSelectInputDefine({
@@ -63,6 +76,13 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
action: DeployCertToAliyunDCDN.prototype.onGetDomainList.name,
watches: ['certDomains', 'accessId'],
required: true,
+ mergeScript: `
+ return {
+ show: ctx.compute(({form})=>{
+ return domainMatchMode === "manual"
+ })
+ }
+ `,
})
)
domainName!: string | string[];
@@ -71,15 +91,30 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
async onInstance() { }
async execute(): Promise {
this.logger.info('开始部署证书到阿里云DCDN');
- if (!this.domainName) {
- throw new Error('您还未选择DCDN域名');
- }
const access = (await this.getAccess(this.accessId)) as AliyunAccess;
const client = await this.getClient(access);
- if (typeof this.domainName === 'string') {
- this.domainName = [this.domainName];
+
+ let domains: string[] = [];
+
+ if (this.domainMatchMode === 'auto') {
+ this.logger.info('使用根据证书匹配模式');
+ if (!this.certDomains || this.certDomains.length === 0) {
+ throw new Error('未获取到证书域名信息');
+ }
+ domains = await this.getAutoMatchedDomains(this.certDomains);
+ if (domains.length === 0) {
+ this.logger.warn('未找到匹配的DCDN域名');
+ return;
+ }
+ this.logger.info(`找到 ${domains.length} 个匹配的DCDN域名`);
+ } else {
+ if (!this.domainName) {
+ throw new Error('您还未选择DCDN域名');
+ }
+ domains = typeof this.domainName === 'string' ? [this.domainName] : this.domainName;
}
- for (const domainName of this.domainName) {
+
+ for (const domainName of domains) {
this.logger.info(`[${domainName}]开始部署`)
const params = await this.buildParams(domainName);
await this.doRequest(client, params);
@@ -152,7 +187,36 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
}
- async onGetDomainList(data: any) {
+ async getAutoMatchedDomains(certDomains: string[]): Promise {
+ const matchedDomains: string[] = [];
+ let pageNumber = 1;
+
+ while (true) {
+ const result = await this.onGetDomainList({ pageNo: pageNumber });
+ const pageData = result.list;
+ this.logger.info(`获取到 ${pageData.length} 个DCDN域名`);
+
+ if (!pageData || pageData.length === 0) {
+ break;
+ }
+
+ const matched = this.getMatchedDomains(pageData, certDomains);
+ matchedDomains.push(...matched);
+
+ const totalCount = result.total || 0;
+ if (pageNumber * 500 >= totalCount) {
+ break;
+ }
+
+ pageNumber++;
+ }
+
+ return matchedDomains;
+ }
+
+
+
+ async onGetDomainList(data: PageSearch) {
if (!this.accessId) {
throw new Error('请选择Access授权');
}
@@ -161,7 +225,7 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
const client = await this.getClient(access);
const params = {
- // 'DomainName': 'aaa',
+ PageNumber: data.pageNo || 1,
PageSize: 500,
};
@@ -172,10 +236,9 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
const res = await client.request('DescribeDcdnUserDomains', params, requestOption);
this.checkRet(res);
- const pageData = res?.Domains?.PageData;
- if (!pageData || pageData.length === 0) {
- throw new Error('找不到CDN域名,您可以手动输入');
- }
+ const pageData = res?.Domains?.PageData || [];
+ const total = res?.Domains?.TotalCount || 0;
+
const options = pageData.map((item: any) => {
return {
value: item.DomainName,
@@ -183,7 +246,11 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
domain: item.DomainName,
};
});
- return optionsUtils.buildGroupOptions(options, this.certDomains);
+
+ return {
+ list: optionsUtils.buildGroupOptions(options, this.certDomains),
+ total: total,
+ };
}
}
new DeployCertToAliyunDCDN();