@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.
- package/.claude-plugin/marketplace.json +3 -3
- package/.github/workflows/publish.yml +20 -10
- package/.releaserc.json +31 -0
- package/AGENTS.md +6 -1
- package/CHANGELOG.md +76 -0
- package/CLAUDE.md +16 -3
- package/README.md +83 -16
- package/bun.lock +977 -0
- package/docs/plans/2026-02-14-feat-auto-detect-install-and-gemini-sync-plan.md +360 -0
- package/docs/plans/2026-02-25-feat-windsurf-global-scope-support-plan.md +627 -0
- package/docs/plans/2026-03-01-feat-ce-command-aliases-backwards-compatible-deprecation-plan.md +261 -0
- package/docs/plans/2026-03-01-fix-setup-skill-non-claude-llm-fallback-plan.md +140 -0
- package/docs/plans/2026-03-03-feat-sync-claude-mcp-all-supported-providers-plan.md +639 -0
- package/docs/plans/feature_opencode-commands-as-md-and-config-merge.md +574 -0
- package/docs/solutions/adding-converter-target-providers.md +693 -0
- package/docs/solutions/plugin-versioning-requirements.md +7 -3
- package/docs/specs/windsurf.md +477 -0
- package/package.json +10 -4
- package/plans/landing-page-launchkit-refresh.md +2 -2
- package/plugins/compound-engineering/.claude-plugin/plugin.json +2 -2
- package/plugins/compound-engineering/CHANGELOG.md +82 -1
- package/plugins/compound-engineering/CLAUDE.md +14 -7
- package/plugins/compound-engineering/README.md +10 -7
- package/plugins/compound-engineering/agents/research/git-history-analyzer.md +1 -1
- package/plugins/compound-engineering/agents/research/learnings-researcher.md +1 -1
- package/plugins/compound-engineering/agents/review/code-simplicity-reviewer.md +1 -1
- package/plugins/compound-engineering/commands/ce/brainstorm.md +145 -0
- package/plugins/compound-engineering/commands/ce/compound.md +240 -0
- package/plugins/compound-engineering/commands/ce/plan.md +636 -0
- package/plugins/compound-engineering/commands/ce/review.md +525 -0
- package/plugins/compound-engineering/commands/ce/work.md +470 -0
- package/plugins/compound-engineering/commands/create-agent-skill.md +1 -1
- package/plugins/compound-engineering/commands/deepen-plan.md +6 -6
- package/plugins/compound-engineering/commands/deploy-docs.md +1 -1
- package/plugins/compound-engineering/commands/feature-video.md +15 -6
- package/plugins/compound-engineering/commands/heal-skill.md +1 -1
- package/plugins/compound-engineering/commands/lfg.md +3 -3
- package/plugins/compound-engineering/commands/slfg.md +3 -3
- package/plugins/compound-engineering/commands/test-xcode.md +2 -2
- package/plugins/compound-engineering/commands/workflows/brainstorm.md +4 -123
- package/plugins/compound-engineering/commands/workflows/compound.md +4 -234
- package/plugins/compound-engineering/commands/workflows/plan.md +4 -562
- package/plugins/compound-engineering/commands/workflows/review.md +4 -522
- package/plugins/compound-engineering/commands/workflows/work.md +4 -448
- package/plugins/compound-engineering/skills/brainstorming/SKILL.md +3 -3
- package/plugins/compound-engineering/skills/create-agent-skills/workflows/add-workflow.md +6 -0
- package/plugins/compound-engineering/skills/create-agent-skills/workflows/create-new-skill.md +6 -0
- package/plugins/compound-engineering/skills/document-review/SKILL.md +1 -1
- package/plugins/compound-engineering/skills/file-todos/SKILL.md +1 -1
- package/plugins/compound-engineering/skills/git-worktree/SKILL.md +5 -5
- package/plugins/compound-engineering/skills/proof/SKILL.md +185 -0
- package/plugins/compound-engineering/skills/resolve-pr-parallel/SKILL.md +1 -1
- package/plugins/compound-engineering/skills/setup/SKILL.md +8 -2
- package/src/commands/convert.ts +101 -24
- package/src/commands/install.ts +102 -45
- package/src/commands/sync.ts +43 -62
- package/src/converters/claude-to-openclaw.ts +240 -0
- package/src/converters/claude-to-opencode.ts +12 -10
- package/src/converters/claude-to-qwen.ts +238 -0
- package/src/converters/claude-to-windsurf.ts +205 -0
- package/src/index.ts +2 -1
- package/src/parsers/claude-home.ts +55 -3
- package/src/sync/codex.ts +38 -62
- package/src/sync/commands.ts +198 -0
- package/src/sync/copilot.ts +14 -36
- package/src/sync/droid.ts +50 -9
- package/src/sync/gemini.ts +135 -0
- package/src/sync/json-config.ts +47 -0
- package/src/sync/kiro.ts +49 -0
- package/src/sync/mcp-transports.ts +19 -0
- package/src/sync/openclaw.ts +18 -0
- package/src/sync/opencode.ts +10 -30
- package/src/sync/pi.ts +12 -36
- package/src/sync/qwen.ts +66 -0
- package/src/sync/registry.ts +141 -0
- package/src/sync/skills.ts +21 -0
- package/src/sync/windsurf.ts +59 -0
- package/src/targets/index.ts +60 -1
- package/src/targets/openclaw.ts +96 -0
- package/src/targets/opencode.ts +76 -10
- package/src/targets/qwen.ts +64 -0
- package/src/targets/windsurf.ts +104 -0
- package/src/types/kiro.ts +3 -1
- package/src/types/openclaw.ts +52 -0
- package/src/types/opencode.ts +7 -8
- package/src/types/qwen.ts +51 -0
- package/src/types/windsurf.ts +35 -0
- package/src/utils/codex-agents.ts +1 -1
- package/src/utils/detect-tools.ts +37 -0
- package/src/utils/files.ts +14 -0
- package/src/utils/resolve-output.ts +50 -0
- package/src/utils/secrets.ts +24 -0
- package/src/utils/symlink.ts +4 -6
- package/tests/claude-home.test.ts +46 -0
- package/tests/cli.test.ts +180 -0
- package/tests/converter.test.ts +43 -10
- package/tests/detect-tools.test.ts +119 -0
- package/tests/openclaw-converter.test.ts +200 -0
- package/tests/opencode-writer.test.ts +142 -5
- package/tests/qwen-converter.test.ts +238 -0
- package/tests/resolve-output.test.ts +131 -0
- package/tests/sync-codex.test.ts +64 -0
- package/tests/sync-copilot.test.ts +60 -4
- package/tests/sync-droid.test.ts +44 -4
- package/tests/sync-gemini.test.ts +160 -0
- package/tests/sync-kiro.test.ts +83 -0
- package/tests/sync-openclaw.test.ts +51 -0
- package/tests/sync-qwen.test.ts +75 -0
- package/tests/sync-windsurf.test.ts +89 -0
- package/tests/windsurf-converter.test.ts +573 -0
- package/tests/windsurf-writer.test.ts +359 -0
- package/docs/css/docs.css +0 -675
- package/docs/css/style.css +0 -2886
- package/docs/index.html +0 -1046
- package/docs/js/main.js +0 -225
- package/docs/pages/agents.html +0 -649
- package/docs/pages/changelog.html +0 -534
- package/docs/pages/commands.html +0 -523
- package/docs/pages/getting-started.html +0 -582
- package/docs/pages/mcp-servers.html +0 -409
- 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:
|
|
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
|
-
|
|
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 /
|
|
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"
|
package/src/commands/convert.ts
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
117
|
-
|
|
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
|
-
}
|
package/src/commands/install.ts
CHANGED
|
@@ -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: "
|
|
52
|
-
description: "Permission mapping: none | broad | from-
|
|
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
|
|
99
|
-
|
|
100
|
-
|
|
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(
|
|
121
|
-
|
|
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()
|