@every-env/compound-plugin 0.9.0 → 2.34.2

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.
Files changed (121) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.github/workflows/publish.yml +20 -10
  3. package/.releaserc.json +31 -0
  4. package/AGENTS.md +6 -1
  5. package/CHANGELOG.md +76 -0
  6. package/CLAUDE.md +16 -3
  7. package/README.md +83 -16
  8. package/bun.lock +977 -0
  9. package/docs/plans/2026-02-14-feat-auto-detect-install-and-gemini-sync-plan.md +360 -0
  10. package/docs/plans/2026-02-25-feat-windsurf-global-scope-support-plan.md +627 -0
  11. package/docs/plans/2026-03-01-feat-ce-command-aliases-backwards-compatible-deprecation-plan.md +261 -0
  12. package/docs/plans/2026-03-01-fix-setup-skill-non-claude-llm-fallback-plan.md +140 -0
  13. package/docs/plans/2026-03-03-feat-sync-claude-mcp-all-supported-providers-plan.md +639 -0
  14. package/docs/plans/feature_opencode-commands-as-md-and-config-merge.md +574 -0
  15. package/docs/solutions/adding-converter-target-providers.md +693 -0
  16. package/docs/solutions/plugin-versioning-requirements.md +7 -3
  17. package/docs/specs/windsurf.md +477 -0
  18. package/package.json +10 -4
  19. package/plans/landing-page-launchkit-refresh.md +2 -2
  20. package/plugins/compound-engineering/.claude-plugin/plugin.json +2 -2
  21. package/plugins/compound-engineering/CHANGELOG.md +82 -1
  22. package/plugins/compound-engineering/CLAUDE.md +14 -7
  23. package/plugins/compound-engineering/README.md +10 -7
  24. package/plugins/compound-engineering/agents/research/git-history-analyzer.md +1 -1
  25. package/plugins/compound-engineering/agents/research/learnings-researcher.md +1 -1
  26. package/plugins/compound-engineering/agents/review/code-simplicity-reviewer.md +1 -1
  27. package/plugins/compound-engineering/commands/ce/brainstorm.md +145 -0
  28. package/plugins/compound-engineering/commands/ce/compound.md +240 -0
  29. package/plugins/compound-engineering/commands/ce/plan.md +636 -0
  30. package/plugins/compound-engineering/commands/ce/review.md +525 -0
  31. package/plugins/compound-engineering/commands/ce/work.md +470 -0
  32. package/plugins/compound-engineering/commands/create-agent-skill.md +1 -1
  33. package/plugins/compound-engineering/commands/deepen-plan.md +6 -6
  34. package/plugins/compound-engineering/commands/deploy-docs.md +1 -1
  35. package/plugins/compound-engineering/commands/feature-video.md +15 -6
  36. package/plugins/compound-engineering/commands/heal-skill.md +1 -1
  37. package/plugins/compound-engineering/commands/lfg.md +3 -3
  38. package/plugins/compound-engineering/commands/slfg.md +3 -3
  39. package/plugins/compound-engineering/commands/test-xcode.md +2 -2
  40. package/plugins/compound-engineering/commands/workflows/brainstorm.md +4 -123
  41. package/plugins/compound-engineering/commands/workflows/compound.md +4 -234
  42. package/plugins/compound-engineering/commands/workflows/plan.md +4 -562
  43. package/plugins/compound-engineering/commands/workflows/review.md +4 -522
  44. package/plugins/compound-engineering/commands/workflows/work.md +4 -448
  45. package/plugins/compound-engineering/skills/brainstorming/SKILL.md +3 -3
  46. package/plugins/compound-engineering/skills/create-agent-skills/workflows/add-workflow.md +6 -0
  47. package/plugins/compound-engineering/skills/create-agent-skills/workflows/create-new-skill.md +6 -0
  48. package/plugins/compound-engineering/skills/document-review/SKILL.md +1 -1
  49. package/plugins/compound-engineering/skills/file-todos/SKILL.md +1 -1
  50. package/plugins/compound-engineering/skills/git-worktree/SKILL.md +5 -5
  51. package/plugins/compound-engineering/skills/proof/SKILL.md +185 -0
  52. package/plugins/compound-engineering/skills/resolve-pr-parallel/SKILL.md +1 -1
  53. package/plugins/compound-engineering/skills/setup/SKILL.md +8 -2
  54. package/src/commands/convert.ts +101 -24
  55. package/src/commands/install.ts +102 -45
  56. package/src/commands/sync.ts +43 -62
  57. package/src/converters/claude-to-openclaw.ts +240 -0
  58. package/src/converters/claude-to-opencode.ts +12 -10
  59. package/src/converters/claude-to-qwen.ts +238 -0
  60. package/src/converters/claude-to-windsurf.ts +205 -0
  61. package/src/index.ts +2 -1
  62. package/src/parsers/claude-home.ts +55 -3
  63. package/src/sync/codex.ts +38 -62
  64. package/src/sync/commands.ts +198 -0
  65. package/src/sync/copilot.ts +14 -36
  66. package/src/sync/droid.ts +50 -9
  67. package/src/sync/gemini.ts +135 -0
  68. package/src/sync/json-config.ts +47 -0
  69. package/src/sync/kiro.ts +49 -0
  70. package/src/sync/mcp-transports.ts +19 -0
  71. package/src/sync/openclaw.ts +18 -0
  72. package/src/sync/opencode.ts +10 -30
  73. package/src/sync/pi.ts +12 -36
  74. package/src/sync/qwen.ts +66 -0
  75. package/src/sync/registry.ts +141 -0
  76. package/src/sync/skills.ts +21 -0
  77. package/src/sync/windsurf.ts +59 -0
  78. package/src/targets/index.ts +60 -1
  79. package/src/targets/openclaw.ts +96 -0
  80. package/src/targets/opencode.ts +76 -10
  81. package/src/targets/qwen.ts +64 -0
  82. package/src/targets/windsurf.ts +104 -0
  83. package/src/types/kiro.ts +3 -1
  84. package/src/types/openclaw.ts +52 -0
  85. package/src/types/opencode.ts +7 -8
  86. package/src/types/qwen.ts +51 -0
  87. package/src/types/windsurf.ts +35 -0
  88. package/src/utils/codex-agents.ts +1 -1
  89. package/src/utils/detect-tools.ts +37 -0
  90. package/src/utils/files.ts +14 -0
  91. package/src/utils/resolve-output.ts +50 -0
  92. package/src/utils/secrets.ts +24 -0
  93. package/src/utils/symlink.ts +4 -6
  94. package/tests/claude-home.test.ts +46 -0
  95. package/tests/cli.test.ts +180 -0
  96. package/tests/converter.test.ts +43 -10
  97. package/tests/detect-tools.test.ts +119 -0
  98. package/tests/openclaw-converter.test.ts +200 -0
  99. package/tests/opencode-writer.test.ts +142 -5
  100. package/tests/qwen-converter.test.ts +238 -0
  101. package/tests/resolve-output.test.ts +131 -0
  102. package/tests/sync-codex.test.ts +64 -0
  103. package/tests/sync-copilot.test.ts +60 -4
  104. package/tests/sync-droid.test.ts +44 -4
  105. package/tests/sync-gemini.test.ts +160 -0
  106. package/tests/sync-kiro.test.ts +83 -0
  107. package/tests/sync-openclaw.test.ts +51 -0
  108. package/tests/sync-qwen.test.ts +75 -0
  109. package/tests/sync-windsurf.test.ts +89 -0
  110. package/tests/windsurf-converter.test.ts +573 -0
  111. package/tests/windsurf-writer.test.ts +359 -0
  112. package/docs/css/docs.css +0 -675
  113. package/docs/css/style.css +0 -2886
  114. package/docs/index.html +0 -1046
  115. package/docs/js/main.js +0 -225
  116. package/docs/pages/agents.html +0 -649
  117. package/docs/pages/changelog.html +0 -534
  118. package/docs/pages/commands.html +0 -523
  119. package/docs/pages/getting-started.html +0 -582
  120. package/docs/pages/mcp-servers.html +0 -409
  121. package/docs/pages/skills.html +0 -611
@@ -0,0 +1,185 @@
1
+ ---
2
+ name: proof
3
+ description: Create, edit, comment on, and share markdown documents via Proof's web API and local bridge. Use when asked to "proof", "share a doc", "create a proof doc", "comment on a document", "suggest edits", "review in proof", or when given a proofeditor.ai URL.
4
+ allowed-tools:
5
+ - Bash
6
+ - Read
7
+ - Write
8
+ - WebFetch
9
+ ---
10
+
11
+ # Proof - Collaborative Markdown Editor
12
+
13
+ Proof is a collaborative document editor for humans and agents. It supports two modes:
14
+
15
+ 1. **Web API** - Create and edit shared documents via HTTP (no install needed)
16
+ 2. **Local Bridge** - Drive the macOS Proof app via localhost:9847
17
+
18
+ ## Web API (Primary for Sharing)
19
+
20
+ ### Create a Shared Document
21
+
22
+ No authentication required. Returns a shareable URL with access token.
23
+
24
+ ```bash
25
+ curl -X POST https://www.proofeditor.ai/share/markdown \
26
+ -H "Content-Type: application/json" \
27
+ -d '{"title":"My Doc","markdown":"# Hello\n\nContent here."}'
28
+ ```
29
+
30
+ **Response format:**
31
+ ```json
32
+ {
33
+ "slug": "abc123",
34
+ "tokenUrl": "https://www.proofeditor.ai/d/abc123?token=xxx",
35
+ "accessToken": "xxx",
36
+ "ownerSecret": "yyy",
37
+ "_links": {
38
+ "state": "https://www.proofeditor.ai/api/agent/abc123/state",
39
+ "ops": "https://www.proofeditor.ai/api/agent/abc123/ops"
40
+ }
41
+ }
42
+ ```
43
+
44
+ Use the `tokenUrl` as the shareable link. The `_links` give you the exact API paths.
45
+
46
+ ### Read a Shared Document
47
+
48
+ ```bash
49
+ curl -s "https://www.proofeditor.ai/api/agent/{slug}/state" \
50
+ -H "x-share-token: <token>"
51
+ ```
52
+
53
+ ### Edit a Shared Document
54
+
55
+ All operations go to `POST https://www.proofeditor.ai/api/agent/{slug}/ops`
56
+
57
+ **Note:** Use the `/api/agent/{slug}/ops` path (from `_links` in create response), NOT `/api/documents/{slug}/ops`.
58
+
59
+ **Authentication for protected docs:**
60
+ - Header: `x-share-token: <token>` or `Authorization: Bearer <token>`
61
+ - Token comes from the URL parameter: `?token=xxx` or the `accessToken` from create response
62
+
63
+ **Comment on text:**
64
+ ```json
65
+ {"op": "comment.add", "quote": "text to comment on", "by": "ai:<agent-name>", "text": "Your comment here"}
66
+ ```
67
+
68
+ **Reply to a comment:**
69
+ ```json
70
+ {"op": "comment.reply", "markId": "<id>", "by": "ai:<agent-name>", "text": "Reply text"}
71
+ ```
72
+
73
+ **Resolve a comment:**
74
+ ```json
75
+ {"op": "comment.resolve", "markId": "<id>", "by": "ai:<agent-name>"}
76
+ ```
77
+
78
+ **Suggest a replacement:**
79
+ ```json
80
+ {"op": "suggestion.add", "kind": "replace", "quote": "original text", "by": "ai:<agent-name>", "content": "replacement text"}
81
+ ```
82
+
83
+ **Suggest a deletion:**
84
+ ```json
85
+ {"op": "suggestion.add", "kind": "delete", "quote": "text to delete", "by": "ai:<agent-name>"}
86
+ ```
87
+
88
+ **Bulk rewrite:**
89
+ ```json
90
+ {"op": "rewrite.apply", "content": "full new markdown", "by": "ai:<agent-name>"}
91
+ ```
92
+
93
+ ### Known Limitations (Web API)
94
+
95
+ - `suggestion.add` with `kind: "insert"` returns Bad Request on the web ops endpoint. Use `kind: "replace"` with a broader quote instead, or use `rewrite.apply` for insertions.
96
+ - Bridge-style endpoints (`/d/{slug}/bridge/*`) require client version headers (`x-proof-client-version`, `x-proof-client-build`, `x-proof-client-protocol`) and return 426 CLIENT_UPGRADE_REQUIRED without them. Use the `/api/agent/{slug}/ops` endpoint instead.
97
+
98
+ ## Local Bridge (macOS App)
99
+
100
+ Requires Proof.app running. Bridge at `http://localhost:9847`.
101
+
102
+ **Required headers:**
103
+ - `X-Agent-Id: claude` (identity for presence)
104
+ - `Content-Type: application/json`
105
+ - `X-Window-Id: <uuid>` (when multiple docs open)
106
+
107
+ ### Key Endpoints
108
+
109
+ | Method | Endpoint | Purpose |
110
+ |--------|----------|---------|
111
+ | GET | `/windows` | List open documents |
112
+ | GET | `/state` | Read markdown, cursor, word count |
113
+ | GET | `/marks` | List all suggestions and comments |
114
+ | POST | `/marks/suggest-replace` | `{"quote":"old","by":"ai:<agent-name>","content":"new"}` |
115
+ | POST | `/marks/suggest-insert` | `{"quote":"after this","by":"ai:<agent-name>","content":"insert"}` |
116
+ | POST | `/marks/suggest-delete` | `{"quote":"delete this","by":"ai:<agent-name>"}` |
117
+ | POST | `/marks/comment` | `{"quote":"text","by":"ai:<agent-name>","text":"comment"}` |
118
+ | POST | `/marks/reply` | `{"markId":"<id>","by":"ai:<agent-name>","text":"reply"}` |
119
+ | POST | `/marks/resolve` | `{"markId":"<id>","by":"ai:<agent-name>"}` |
120
+ | POST | `/marks/accept` | `{"markId":"<id>"}` |
121
+ | POST | `/marks/reject` | `{"markId":"<id>"}` |
122
+ | POST | `/rewrite` | `{"content":"full markdown","by":"ai:<agent-name>"}` |
123
+ | POST | `/presence` | `{"status":"reading","summary":"..."}` |
124
+ | GET | `/events/pending` | Poll for user actions |
125
+
126
+ ### Presence Statuses
127
+
128
+ `thinking`, `reading`, `idle`, `acting`, `waiting`, `completed`
129
+
130
+ ## Workflow: Review a Shared Document
131
+
132
+ When given a Proof URL like `https://www.proofeditor.ai/d/abc123?token=xxx`:
133
+
134
+ 1. Extract the slug (`abc123`) and token from the URL
135
+ 2. Read the document state via the API
136
+ 3. Add comments or suggest edits using the ops endpoint
137
+ 4. The author sees changes in real-time
138
+
139
+ ```bash
140
+ # Read
141
+ curl -s "https://www.proofeditor.ai/api/agent/abc123/state" \
142
+ -H "x-share-token: xxx"
143
+
144
+ # Comment
145
+ curl -X POST "https://www.proofeditor.ai/api/agent/abc123/ops" \
146
+ -H "Content-Type: application/json" \
147
+ -H "x-share-token: xxx" \
148
+ -d '{"op":"comment.add","quote":"text","by":"ai:compound","text":"comment"}'
149
+
150
+ # Suggest edit
151
+ curl -X POST "https://www.proofeditor.ai/api/agent/abc123/ops" \
152
+ -H "Content-Type: application/json" \
153
+ -H "x-share-token: xxx" \
154
+ -d '{"op":"suggestion.add","kind":"replace","quote":"old","by":"ai:compound","content":"new"}'
155
+ ```
156
+
157
+ ## Workflow: Create and Share a New Document
158
+
159
+ ```bash
160
+ # 1. Create
161
+ RESPONSE=$(curl -s -X POST https://www.proofeditor.ai/share/markdown \
162
+ -H "Content-Type: application/json" \
163
+ -d '{"title":"My Doc","markdown":"# Title\n\nContent here."}')
164
+
165
+ # 2. Extract URL and token
166
+ URL=$(echo "$RESPONSE" | jq -r '.tokenUrl')
167
+ SLUG=$(echo "$RESPONSE" | jq -r '.slug')
168
+ TOKEN=$(echo "$RESPONSE" | jq -r '.accessToken')
169
+
170
+ # 3. Share the URL
171
+ echo "$URL"
172
+
173
+ # 4. Make edits using the ops endpoint
174
+ curl -X POST "https://www.proofeditor.ai/api/agent/$SLUG/ops" \
175
+ -H "Content-Type: application/json" \
176
+ -H "x-share-token: $TOKEN" \
177
+ -d '{"op":"comment.add","quote":"Content here","by":"ai:compound","text":"Added a note"}'
178
+ ```
179
+
180
+ ## Safety
181
+
182
+ - Use `/state` content as source of truth before editing
183
+ - Prefer suggest-replace over full rewrite for small changes
184
+ - Don't span table cells in a single replace
185
+ - Always include `by` field for attribution tracking
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: resolve_pr_parallel
2
+ name: resolve-pr-parallel
3
3
  description: Resolve all PR comments using parallel processing. Use when addressing PR review feedback, resolving review threads, or batch-fixing PR comments.
4
4
  argument-hint: "[optional: PR number or current PR]"
5
5
  disable-model-invocation: true
@@ -6,7 +6,13 @@ disable-model-invocation: true
6
6
 
7
7
  # Compound Engineering Setup
8
8
 
9
- Interactive setup for `compound-engineering.local.md` — configures which agents run during `/workflows:review` and `/workflows:work`.
9
+ ## Interaction Method
10
+
11
+ If `AskUserQuestion` is available, use it for all prompts below.
12
+
13
+ If not, present each question as a numbered list and wait for a reply before proceeding to the next step. For multiSelect questions, accept comma-separated numbers (e.g. `1, 3`). Never skip or auto-configure.
14
+
15
+ Interactive setup for `compound-engineering.local.md` — configures which agents run during `/ce:review` and `/ce:work`.
10
16
 
11
17
  ## Step 1: Check Existing Config
12
18
 
@@ -145,7 +151,7 @@ plan_review_agents: [{computed plan agent list}]
145
151
  # Review Context
146
152
 
147
153
  Add project-specific review instructions here.
148
- These notes are passed to all review agents during /workflows:review and /workflows:work.
154
+ These notes are passed to all review agents during /ce:review and /ce:work.
149
155
 
150
156
  Examples:
151
157
  - "We use Turbo Frames heavily — check for frame-busting issues"
@@ -2,10 +2,12 @@ import { defineCommand } from "citty"
2
2
  import os from "os"
3
3
  import path from "path"
4
4
  import { loadClaudePlugin } from "../parsers/claude"
5
- import { targets } from "../targets"
5
+ import { targets, validateScope } from "../targets"
6
6
  import type { PermissionMode } from "../converters/claude-to-opencode"
7
7
  import { ensureCodexAgentsFile } from "../utils/codex-agents"
8
8
  import { expandHome, resolveTargetHome } from "../utils/resolve-home"
9
+ import { resolveTargetOutputRoot } from "../utils/resolve-output"
10
+ import { detectInstalledTools } from "../utils/detect-tools"
9
11
 
10
12
  const permissionModes: PermissionMode[] = ["none", "broad", "from-commands"]
11
13
 
@@ -23,7 +25,7 @@ export default defineCommand({
23
25
  to: {
24
26
  type: "string",
25
27
  default: "opencode",
26
- description: "Target format (opencode | codex | droid | cursor | pi | gemini | kiro)",
28
+ description: "Target format (opencode | codex | droid | cursor | pi | copilot | gemini | kiro | windsurf | openclaw | qwen | all)",
27
29
  },
28
30
  output: {
29
31
  type: "string",
@@ -40,6 +42,20 @@ export default defineCommand({
40
42
  alias: "pi-home",
41
43
  description: "Write Pi output to this Pi root (ex: ~/.pi/agent or ./.pi)",
42
44
  },
45
+ openclawHome: {
46
+ type: "string",
47
+ alias: "openclaw-home",
48
+ description: "Write OpenClaw output to this extensions root (ex: ~/.openclaw/extensions)",
49
+ },
50
+ qwenHome: {
51
+ type: "string",
52
+ alias: "qwen-home",
53
+ description: "Write Qwen output to this Qwen extensions root (ex: ~/.qwen/extensions)",
54
+ },
55
+ scope: {
56
+ type: "string",
57
+ description: "Scope level: global | workspace (default varies by target)",
58
+ },
43
59
  also: {
44
60
  type: "string",
45
61
  description: "Comma-separated extra targets to generate (ex: codex)",
@@ -62,14 +78,6 @@ export default defineCommand({
62
78
  },
63
79
  async run({ args }) {
64
80
  const targetName = String(args.to)
65
- const target = targets[targetName]
66
- if (!target) {
67
- throw new Error(`Unknown target: ${targetName}`)
68
- }
69
-
70
- if (!target.implemented) {
71
- throw new Error(`Target ${targetName} is registered but not implemented yet.`)
72
- }
73
81
 
74
82
  const permissions = String(args.permissions)
75
83
  if (!permissionModes.includes(permissions as PermissionMode)) {
@@ -78,8 +86,11 @@ export default defineCommand({
78
86
 
79
87
  const plugin = await loadClaudePlugin(String(args.source))
80
88
  const outputRoot = resolveOutputRoot(args.output)
89
+ const hasExplicitOutput = Boolean(args.output && String(args.output).trim())
81
90
  const codexHome = resolveTargetHome(args.codexHome, path.join(os.homedir(), ".codex"))
82
91
  const piHome = resolveTargetHome(args.piHome, path.join(os.homedir(), ".pi", "agent"))
92
+ const openclawHome = resolveTargetHome(args.openclawHome, path.join(os.homedir(), ".openclaw", "extensions"))
93
+ const qwenHome = resolveTargetHome(args.qwenHome, path.join(os.homedir(), ".qwen", "extensions"))
83
94
 
84
95
  const options = {
85
96
  agentMode: String(args.agentMode) === "primary" ? "primary" : "subagent",
@@ -87,13 +98,79 @@ export default defineCommand({
87
98
  permissions: permissions as PermissionMode,
88
99
  }
89
100
 
90
- const primaryOutputRoot = resolveTargetOutputRoot(targetName, outputRoot, codexHome, piHome)
101
+ if (targetName === "all") {
102
+ const detected = await detectInstalledTools()
103
+ const activeTargets = detected.filter((t) => t.detected)
104
+
105
+ if (activeTargets.length === 0) {
106
+ console.log("No AI coding tools detected. Install at least one tool first.")
107
+ return
108
+ }
109
+
110
+ console.log(`Detected ${activeTargets.length} tool(s):`)
111
+ for (const tool of detected) {
112
+ console.log(` ${tool.detected ? "✓" : "✗"} ${tool.name} — ${tool.reason}`)
113
+ }
114
+
115
+ for (const tool of activeTargets) {
116
+ const handler = targets[tool.name]
117
+ if (!handler || !handler.implemented) {
118
+ console.warn(`Skipping ${tool.name}: not implemented.`)
119
+ continue
120
+ }
121
+ const bundle = handler.convert(plugin, options)
122
+ if (!bundle) {
123
+ console.warn(`Skipping ${tool.name}: no output returned.`)
124
+ continue
125
+ }
126
+ const root = resolveTargetOutputRoot({
127
+ targetName: tool.name,
128
+ outputRoot,
129
+ codexHome,
130
+ piHome,
131
+ openclawHome,
132
+ qwenHome,
133
+ pluginName: plugin.manifest.name,
134
+ hasExplicitOutput,
135
+ })
136
+ await handler.write(root, bundle)
137
+ console.log(`Converted ${plugin.manifest.name} to ${tool.name} at ${root}`)
138
+ }
139
+
140
+ if (activeTargets.some((t) => t.name === "codex")) {
141
+ await ensureCodexAgentsFile(codexHome)
142
+ }
143
+ return
144
+ }
145
+
146
+ const target = targets[targetName]
147
+ if (!target) {
148
+ throw new Error(`Unknown target: ${targetName}`)
149
+ }
150
+
151
+ if (!target.implemented) {
152
+ throw new Error(`Target ${targetName} is registered but not implemented yet.`)
153
+ }
154
+
155
+ const resolvedScope = validateScope(targetName, target, args.scope ? String(args.scope) : undefined)
156
+
157
+ const primaryOutputRoot = resolveTargetOutputRoot({
158
+ targetName,
159
+ outputRoot,
160
+ codexHome,
161
+ piHome,
162
+ openclawHome,
163
+ qwenHome,
164
+ pluginName: plugin.manifest.name,
165
+ hasExplicitOutput,
166
+ scope: resolvedScope,
167
+ })
91
168
  const bundle = target.convert(plugin, options)
92
169
  if (!bundle) {
93
170
  throw new Error(`Target ${targetName} did not return a bundle.`)
94
171
  }
95
172
 
96
- await target.write(primaryOutputRoot, bundle)
173
+ await target.write(primaryOutputRoot, bundle, resolvedScope)
97
174
  console.log(`Converted ${plugin.manifest.name} to ${targetName} at ${primaryOutputRoot}`)
98
175
 
99
176
  const extraTargets = parseExtraTargets(args.also)
@@ -113,8 +190,18 @@ export default defineCommand({
113
190
  console.warn(`Skipping ${extra}: no output returned.`)
114
191
  continue
115
192
  }
116
- const extraRoot = resolveTargetOutputRoot(extra, path.join(outputRoot, extra), codexHome, piHome)
117
- await handler.write(extraRoot, extraBundle)
193
+ const extraRoot = resolveTargetOutputRoot({
194
+ targetName: extra,
195
+ outputRoot: path.join(outputRoot, extra),
196
+ codexHome,
197
+ piHome,
198
+ openclawHome,
199
+ qwenHome,
200
+ pluginName: plugin.manifest.name,
201
+ hasExplicitOutput,
202
+ scope: handler.defaultScope,
203
+ })
204
+ await handler.write(extraRoot, extraBundle, handler.defaultScope)
118
205
  console.log(`Converted ${plugin.manifest.name} to ${extra} at ${extraRoot}`)
119
206
  }
120
207
 
@@ -139,13 +226,3 @@ function resolveOutputRoot(value: unknown): string {
139
226
  }
140
227
  return process.cwd()
141
228
  }
142
-
143
- function resolveTargetOutputRoot(targetName: string, outputRoot: string, codexHome: string, piHome: string): string {
144
- if (targetName === "codex") return codexHome
145
- if (targetName === "pi") return piHome
146
- if (targetName === "droid") return path.join(os.homedir(), ".factory")
147
- if (targetName === "cursor") return path.join(outputRoot, ".cursor")
148
- if (targetName === "gemini") return path.join(outputRoot, ".gemini")
149
- if (targetName === "kiro") return path.join(outputRoot, ".kiro")
150
- return outputRoot
151
- }
@@ -3,11 +3,13 @@ import { promises as fs } from "fs"
3
3
  import os from "os"
4
4
  import path from "path"
5
5
  import { loadClaudePlugin } from "../parsers/claude"
6
- import { targets } from "../targets"
6
+ import { targets, validateScope } from "../targets"
7
7
  import { pathExists } from "../utils/files"
8
8
  import type { PermissionMode } from "../converters/claude-to-opencode"
9
9
  import { ensureCodexAgentsFile } from "../utils/codex-agents"
10
10
  import { expandHome, resolveTargetHome } from "../utils/resolve-home"
11
+ import { resolveTargetOutputRoot } from "../utils/resolve-output"
12
+ import { detectInstalledTools } from "../utils/detect-tools"
11
13
 
12
14
  const permissionModes: PermissionMode[] = ["none", "broad", "from-commands"]
13
15
 
@@ -25,7 +27,7 @@ export default defineCommand({
25
27
  to: {
26
28
  type: "string",
27
29
  default: "opencode",
28
- description: "Target format (opencode | codex | droid | cursor | pi | copilot | gemini | kiro)",
30
+ description: "Target format (opencode | codex | droid | cursor | pi | copilot | gemini | kiro | windsurf | openclaw | qwen | all)",
29
31
  },
30
32
  output: {
31
33
  type: "string",
@@ -42,14 +44,28 @@ export default defineCommand({
42
44
  alias: "pi-home",
43
45
  description: "Write Pi output to this Pi root (ex: ~/.pi/agent or ./.pi)",
44
46
  },
47
+ openclawHome: {
48
+ type: "string",
49
+ alias: "openclaw-home",
50
+ description: "Write OpenClaw output to this extensions root (ex: ~/.openclaw/extensions)",
51
+ },
52
+ qwenHome: {
53
+ type: "string",
54
+ alias: "qwen-home",
55
+ description: "Write Qwen output to this Qwen extensions root (ex: ~/.qwen/extensions)",
56
+ },
57
+ scope: {
58
+ type: "string",
59
+ description: "Scope level: global | workspace (default varies by target)",
60
+ },
45
61
  also: {
46
62
  type: "string",
47
63
  description: "Comma-separated extra targets to generate (ex: codex)",
48
64
  },
49
65
  permissions: {
50
66
  type: "string",
51
- default: "broad",
52
- description: "Permission mapping: none | broad | from-commands",
67
+ default: "none", // Default is "none" -- writing global permissions to opencode.json pollutes user config. See ADR-003.
68
+ description: "Permission mapping written to opencode.json: none (default) | broad | from-command",
53
69
  },
54
70
  agentMode: {
55
71
  type: "string",
@@ -64,13 +80,6 @@ export default defineCommand({
64
80
  },
65
81
  async run({ args }) {
66
82
  const targetName = String(args.to)
67
- const target = targets[targetName]
68
- if (!target) {
69
- throw new Error(`Unknown target: ${targetName}`)
70
- }
71
- if (!target.implemented) {
72
- throw new Error(`Target ${targetName} is registered but not implemented yet.`)
73
- }
74
83
 
75
84
  const permissions = String(args.permissions)
76
85
  if (!permissionModes.includes(permissions as PermissionMode)) {
@@ -84,6 +93,9 @@ export default defineCommand({
84
93
  const outputRoot = resolveOutputRoot(args.output)
85
94
  const codexHome = resolveTargetHome(args.codexHome, path.join(os.homedir(), ".codex"))
86
95
  const piHome = resolveTargetHome(args.piHome, path.join(os.homedir(), ".pi", "agent"))
96
+ const hasExplicitOutput = Boolean(args.output && String(args.output).trim())
97
+ const openclawHome = resolveTargetHome(args.openclawHome, path.join(os.homedir(), ".openclaw", "extensions"))
98
+ const qwenHome = resolveTargetHome(args.qwenHome, path.join(os.homedir(), ".qwen", "extensions"))
87
99
 
88
100
  const options = {
89
101
  agentMode: String(args.agentMode) === "primary" ? "primary" : "subagent",
@@ -91,13 +103,77 @@ export default defineCommand({
91
103
  permissions: permissions as PermissionMode,
92
104
  }
93
105
 
106
+ if (targetName === "all") {
107
+ const detected = await detectInstalledTools()
108
+ const activeTargets = detected.filter((t) => t.detected)
109
+
110
+ if (activeTargets.length === 0) {
111
+ console.log("No AI coding tools detected. Install at least one tool first.")
112
+ return
113
+ }
114
+
115
+ console.log(`Detected ${activeTargets.length} tool(s):`)
116
+ for (const tool of detected) {
117
+ console.log(` ${tool.detected ? "✓" : "✗"} ${tool.name} — ${tool.reason}`)
118
+ }
119
+
120
+ for (const tool of activeTargets) {
121
+ const handler = targets[tool.name]
122
+ if (!handler || !handler.implemented) {
123
+ console.warn(`Skipping ${tool.name}: not implemented.`)
124
+ continue
125
+ }
126
+ const bundle = handler.convert(plugin, options)
127
+ if (!bundle) {
128
+ console.warn(`Skipping ${tool.name}: no output returned.`)
129
+ continue
130
+ }
131
+ const root = resolveTargetOutputRoot({
132
+ targetName: tool.name,
133
+ outputRoot,
134
+ codexHome,
135
+ piHome,
136
+ openclawHome,
137
+ qwenHome,
138
+ pluginName: plugin.manifest.name,
139
+ hasExplicitOutput,
140
+ })
141
+ await handler.write(root, bundle)
142
+ console.log(`Installed ${plugin.manifest.name} to ${tool.name} at ${root}`)
143
+ }
144
+
145
+ if (activeTargets.some((t) => t.name === "codex")) {
146
+ await ensureCodexAgentsFile(codexHome)
147
+ }
148
+ return
149
+ }
150
+
151
+ const target = targets[targetName]
152
+ if (!target) {
153
+ throw new Error(`Unknown target: ${targetName}`)
154
+ }
155
+ if (!target.implemented) {
156
+ throw new Error(`Target ${targetName} is registered but not implemented yet.`)
157
+ }
158
+
159
+ const resolvedScope = validateScope(targetName, target, args.scope ? String(args.scope) : undefined)
160
+
94
161
  const bundle = target.convert(plugin, options)
95
162
  if (!bundle) {
96
163
  throw new Error(`Target ${targetName} did not return a bundle.`)
97
164
  }
98
- const hasExplicitOutput = Boolean(args.output && String(args.output).trim())
99
- const primaryOutputRoot = resolveTargetOutputRoot(targetName, outputRoot, codexHome, piHome, hasExplicitOutput)
100
- await target.write(primaryOutputRoot, bundle)
165
+ const primaryOutputRoot = resolveTargetOutputRoot({
166
+ targetName,
167
+ outputRoot,
168
+ codexHome,
169
+ piHome,
170
+ openclawHome,
171
+ qwenHome,
172
+ pluginName: plugin.manifest.name,
173
+ hasExplicitOutput,
174
+ scope: resolvedScope,
175
+ })
176
+ await target.write(primaryOutputRoot, bundle, resolvedScope)
101
177
  console.log(`Installed ${plugin.manifest.name} to ${primaryOutputRoot}`)
102
178
 
103
179
  const extraTargets = parseExtraTargets(args.also)
@@ -117,8 +193,18 @@ export default defineCommand({
117
193
  console.warn(`Skipping ${extra}: no output returned.`)
118
194
  continue
119
195
  }
120
- const extraRoot = resolveTargetOutputRoot(extra, path.join(outputRoot, extra), codexHome, piHome, hasExplicitOutput)
121
- await handler.write(extraRoot, extraBundle)
196
+ const extraRoot = resolveTargetOutputRoot({
197
+ targetName: extra,
198
+ outputRoot: path.join(outputRoot, extra),
199
+ codexHome,
200
+ piHome,
201
+ openclawHome,
202
+ qwenHome,
203
+ pluginName: plugin.manifest.name,
204
+ hasExplicitOutput,
205
+ scope: handler.defaultScope,
206
+ })
207
+ await handler.write(extraRoot, extraBundle, handler.defaultScope)
122
208
  console.log(`Installed ${plugin.manifest.name} to ${extraRoot}`)
123
209
  }
124
210
 
@@ -169,35 +255,6 @@ function resolveOutputRoot(value: unknown): string {
169
255
  return path.join(os.homedir(), ".config", "opencode")
170
256
  }
171
257
 
172
- function resolveTargetOutputRoot(
173
- targetName: string,
174
- outputRoot: string,
175
- codexHome: string,
176
- piHome: string,
177
- hasExplicitOutput: boolean,
178
- ): string {
179
- if (targetName === "codex") return codexHome
180
- if (targetName === "pi") return piHome
181
- if (targetName === "droid") return path.join(os.homedir(), ".factory")
182
- if (targetName === "cursor") {
183
- const base = hasExplicitOutput ? outputRoot : process.cwd()
184
- return path.join(base, ".cursor")
185
- }
186
- if (targetName === "gemini") {
187
- const base = hasExplicitOutput ? outputRoot : process.cwd()
188
- return path.join(base, ".gemini")
189
- }
190
- if (targetName === "copilot") {
191
- const base = hasExplicitOutput ? outputRoot : process.cwd()
192
- return path.join(base, ".github")
193
- }
194
- if (targetName === "kiro") {
195
- const base = hasExplicitOutput ? outputRoot : process.cwd()
196
- return path.join(base, ".kiro")
197
- }
198
- return outputRoot
199
- }
200
-
201
258
  async function resolveGitHubPluginPath(pluginName: string): Promise<ResolvedPluginPath> {
202
259
  const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "compound-plugin-"))
203
260
  const source = resolveGitHubSource()