@orchid-labs/pluxx 0.1.0 → 0.1.3
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/README.md +110 -515
- package/bin/pluxx.js +19 -28
- package/dist/agents.d.ts +16 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/cli/agent.d.ts +69 -0
- package/dist/cli/agent.d.ts.map +1 -1
- package/dist/cli/doctor.d.ts +3 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/entry.d.ts +2 -0
- package/dist/cli/entry.d.ts.map +1 -0
- package/dist/cli/eval.d.ts +22 -0
- package/dist/cli/eval.d.ts.map +1 -0
- package/dist/cli/index.d.ts +26 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +21810 -0
- package/dist/cli/init-from-mcp.d.ts +34 -3
- package/dist/cli/init-from-mcp.d.ts.map +1 -1
- package/dist/cli/install.d.ts +3 -0
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/lint.d.ts +7 -1
- package/dist/cli/lint.d.ts.map +1 -1
- package/dist/cli/mcp-proxy.d.ts +10 -0
- package/dist/cli/mcp-proxy.d.ts.map +1 -0
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/primitive-summary.d.ts +14 -0
- package/dist/cli/primitive-summary.d.ts.map +1 -0
- package/dist/cli/prompt.d.ts +1 -1
- package/dist/cli/publish.d.ts +6 -1
- package/dist/cli/publish.d.ts.map +1 -1
- package/dist/cli/sync-from-mcp.d.ts.map +1 -1
- package/dist/cli/test.d.ts +2 -0
- package/dist/cli/test.d.ts.map +1 -1
- package/dist/cli/verify-install.d.ts +25 -0
- package/dist/cli/verify-install.d.ts.map +1 -0
- package/dist/commands.d.ts +10 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/compiler-intent.d.ts +165 -0
- package/dist/compiler-intent.d.ts.map +1 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/delegation.d.ts +11 -0
- package/dist/delegation.d.ts.map +1 -0
- package/dist/generators/amp/index.d.ts.map +1 -1
- package/dist/generators/base.d.ts +5 -0
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/claude-code/index.d.ts +2 -0
- package/dist/generators/claude-code/index.d.ts.map +1 -1
- package/dist/generators/cline/index.d.ts.map +1 -1
- package/dist/generators/codex/index.d.ts +5 -0
- package/dist/generators/codex/index.d.ts.map +1 -1
- package/dist/generators/cursor/index.d.ts +1 -0
- package/dist/generators/cursor/index.d.ts.map +1 -1
- package/dist/generators/gemini-cli/index.d.ts.map +1 -1
- package/dist/generators/github-copilot/index.d.ts.map +1 -1
- package/dist/generators/opencode/index.d.ts +1 -0
- package/dist/generators/opencode/index.d.ts.map +1 -1
- package/dist/generators/openhands/index.d.ts.map +1 -1
- package/dist/generators/roo-code/index.d.ts.map +1 -1
- package/dist/generators/shared/claude-family.d.ts.map +1 -1
- package/dist/generators/warp/index.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5464 -548
- package/dist/mcp/introspect.d.ts +43 -1
- package/dist/mcp/introspect.d.ts.map +1 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/schema.d.ts +91 -42
- package/dist/schema.d.ts.map +1 -1
- package/dist/text-files.d.ts +5 -0
- package/dist/text-files.d.ts.map +1 -0
- package/dist/validation/platform-rules.d.ts +35 -1
- package/dist/validation/platform-rules.d.ts.map +1 -1
- package/package.json +16 -14
- package/src/cli/agent.ts +0 -1030
- package/src/cli/dev.ts +0 -112
- package/src/cli/doctor.ts +0 -588
- package/src/cli/index.ts +0 -2414
- package/src/cli/init-from-mcp.ts +0 -1611
- package/src/cli/install.ts +0 -698
- package/src/cli/lint.ts +0 -1219
- package/src/cli/migrate.ts +0 -614
- package/src/cli/prompt.ts +0 -82
- package/src/cli/publish.ts +0 -401
- package/src/cli/runtime.ts +0 -86
- package/src/cli/sync-from-mcp.ts +0 -563
- package/src/cli/test.ts +0 -134
- package/src/compatibility/matrix.ts +0 -149
- package/src/config/define.ts +0 -20
- package/src/config/load.ts +0 -74
- package/src/generators/amp/index.ts +0 -63
- package/src/generators/base.ts +0 -188
- package/src/generators/claude-code/index.ts +0 -29
- package/src/generators/cline/index.ts +0 -35
- package/src/generators/codex/index.ts +0 -120
- package/src/generators/cursor/index.ts +0 -158
- package/src/generators/gemini-cli/index.ts +0 -83
- package/src/generators/github-copilot/index.ts +0 -32
- package/src/generators/hooks-warning.ts +0 -51
- package/src/generators/index.ts +0 -71
- package/src/generators/opencode/index.ts +0 -526
- package/src/generators/openhands/index.ts +0 -32
- package/src/generators/roo-code/index.ts +0 -35
- package/src/generators/shared/claude-family.ts +0 -215
- package/src/generators/warp/index.ts +0 -32
- package/src/hook-events.ts +0 -33
- package/src/index.ts +0 -23
- package/src/mcp/introspect.ts +0 -834
- package/src/permissions.ts +0 -258
- package/src/schema.ts +0 -312
- package/src/user-config.ts +0 -177
- package/src/validation/platform-rules.ts +0 -565
package/src/cli/migrate.ts
DELETED
|
@@ -1,614 +0,0 @@
|
|
|
1
|
-
import { resolve, basename, join, relative } from 'path'
|
|
2
|
-
import { existsSync, readdirSync, mkdirSync, cpSync, readFileSync } from 'fs'
|
|
3
|
-
|
|
4
|
-
type DetectedPlatform = 'claude-code' | 'cursor' | 'codex' | 'opencode'
|
|
5
|
-
|
|
6
|
-
interface DetectionResult {
|
|
7
|
-
platform: DetectedPlatform
|
|
8
|
-
manifestPath: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface ParsedManifest {
|
|
12
|
-
name?: string
|
|
13
|
-
version?: string
|
|
14
|
-
description?: string
|
|
15
|
-
author?: { name: string; url?: string; email?: string }
|
|
16
|
-
repository?: string
|
|
17
|
-
license?: string
|
|
18
|
-
keywords?: string[]
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface ParsedMcp {
|
|
22
|
-
[serverName: string]: {
|
|
23
|
-
url?: string
|
|
24
|
-
transport?: 'http' | 'sse' | 'stdio'
|
|
25
|
-
command?: string
|
|
26
|
-
args?: string[]
|
|
27
|
-
env?: Record<string, string>
|
|
28
|
-
auth?: {
|
|
29
|
-
type: 'bearer' | 'header' | 'none'
|
|
30
|
-
envVar?: string
|
|
31
|
-
headerName?: string
|
|
32
|
-
headerTemplate?: string
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface ParsedHooks {
|
|
38
|
-
[event: string]: Array<{
|
|
39
|
-
command: string
|
|
40
|
-
timeout?: number
|
|
41
|
-
matcher?: string
|
|
42
|
-
}>
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface MigrateResult {
|
|
46
|
-
platform: DetectedPlatform
|
|
47
|
-
manifest: ParsedManifest
|
|
48
|
-
mcp: ParsedMcp
|
|
49
|
-
hooks: ParsedHooks
|
|
50
|
-
instructions?: string
|
|
51
|
-
directories: {
|
|
52
|
-
skills: boolean
|
|
53
|
-
commands: boolean
|
|
54
|
-
agents: boolean
|
|
55
|
-
scripts: boolean
|
|
56
|
-
assets: boolean
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ── Platform Detection ──────────────────────────────────────────
|
|
61
|
-
|
|
62
|
-
function detectPlatform(pluginDir: string): DetectionResult | null {
|
|
63
|
-
const checks: Array<{ dir: string; platform: DetectedPlatform }> = [
|
|
64
|
-
{ dir: '.claude-plugin', platform: 'claude-code' },
|
|
65
|
-
{ dir: '.cursor-plugin', platform: 'cursor' },
|
|
66
|
-
{ dir: '.codex-plugin', platform: 'codex' },
|
|
67
|
-
]
|
|
68
|
-
|
|
69
|
-
for (const check of checks) {
|
|
70
|
-
const manifestPath = resolve(pluginDir, check.dir, 'plugin.json')
|
|
71
|
-
if (existsSync(manifestPath)) {
|
|
72
|
-
return { platform: check.platform, manifestPath }
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Check for OpenCode (package.json with @opencode-ai/plugin)
|
|
77
|
-
const pkgPath = resolve(pluginDir, 'package.json')
|
|
78
|
-
if (existsSync(pkgPath)) {
|
|
79
|
-
try {
|
|
80
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
|
81
|
-
const deps = {
|
|
82
|
-
...pkg.dependencies,
|
|
83
|
-
...pkg.devDependencies,
|
|
84
|
-
...pkg.peerDependencies,
|
|
85
|
-
}
|
|
86
|
-
if (deps && '@opencode-ai/plugin' in deps) {
|
|
87
|
-
return { platform: 'opencode', manifestPath: pkgPath }
|
|
88
|
-
}
|
|
89
|
-
} catch {
|
|
90
|
-
// not valid JSON, skip
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return null
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// ── Manifest Parsing ────────────────────────────────────────────
|
|
98
|
-
|
|
99
|
-
function parseManifest(detection: DetectionResult): ParsedManifest {
|
|
100
|
-
const raw = JSON.parse(readFileSync(detection.manifestPath, 'utf-8'))
|
|
101
|
-
|
|
102
|
-
const result: ParsedManifest = {}
|
|
103
|
-
|
|
104
|
-
if (raw.name) result.name = raw.name
|
|
105
|
-
if (raw.version) result.version = raw.version
|
|
106
|
-
if (raw.description) result.description = raw.description
|
|
107
|
-
if (raw.license) result.license = raw.license
|
|
108
|
-
if (raw.keywords) result.keywords = raw.keywords
|
|
109
|
-
if (raw.repository) {
|
|
110
|
-
result.repository = typeof raw.repository === 'string'
|
|
111
|
-
? raw.repository
|
|
112
|
-
: raw.repository?.url
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (raw.author) {
|
|
116
|
-
if (typeof raw.author === 'string') {
|
|
117
|
-
result.author = { name: raw.author }
|
|
118
|
-
} else {
|
|
119
|
-
result.author = {
|
|
120
|
-
name: raw.author.name,
|
|
121
|
-
...(raw.author.url && { url: raw.author.url }),
|
|
122
|
-
...(raw.author.email && { email: raw.author.email }),
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return result
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// ── MCP Parsing ─────────────────────────────────────────────────
|
|
131
|
-
|
|
132
|
-
function parseMcp(pluginDir: string, detection: DetectionResult): ParsedMcp {
|
|
133
|
-
const mcpPaths = [
|
|
134
|
-
resolve(pluginDir, '.mcp.json'),
|
|
135
|
-
resolve(pluginDir, 'mcp.json'),
|
|
136
|
-
]
|
|
137
|
-
|
|
138
|
-
// Also check if the manifest references an mcpServers file
|
|
139
|
-
try {
|
|
140
|
-
const manifest = JSON.parse(readFileSync(detection.manifestPath, 'utf-8'))
|
|
141
|
-
if (manifest.mcpServers && typeof manifest.mcpServers === 'string') {
|
|
142
|
-
mcpPaths.unshift(resolve(pluginDir, manifest.mcpServers))
|
|
143
|
-
}
|
|
144
|
-
} catch {
|
|
145
|
-
// ignore
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
for (const mcpPath of mcpPaths) {
|
|
149
|
-
if (!existsSync(mcpPath)) continue
|
|
150
|
-
try {
|
|
151
|
-
const raw = JSON.parse(readFileSync(mcpPath, 'utf-8'))
|
|
152
|
-
const servers = raw.mcpServers ?? raw
|
|
153
|
-
if (!servers || typeof servers !== 'object') continue
|
|
154
|
-
|
|
155
|
-
const result: ParsedMcp = {}
|
|
156
|
-
|
|
157
|
-
for (const [name, config] of Object.entries(servers)) {
|
|
158
|
-
const cfg = config as Record<string, unknown>
|
|
159
|
-
const entry: ParsedMcp[string] = {}
|
|
160
|
-
|
|
161
|
-
if (cfg.url) entry.url = cfg.url as string
|
|
162
|
-
|
|
163
|
-
// Detect transport
|
|
164
|
-
if (cfg.type === 'stdio' || cfg.command) {
|
|
165
|
-
entry.transport = 'stdio'
|
|
166
|
-
if (cfg.command) entry.command = cfg.command as string
|
|
167
|
-
if (cfg.args) entry.args = cfg.args as string[]
|
|
168
|
-
if (cfg.env) entry.env = cfg.env as Record<string, string>
|
|
169
|
-
} else if (cfg.type === 'sse') {
|
|
170
|
-
entry.transport = 'sse'
|
|
171
|
-
} else {
|
|
172
|
-
entry.transport = 'http'
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Parse auth from headers
|
|
176
|
-
if (cfg.headers && typeof cfg.headers === 'object') {
|
|
177
|
-
const headers = cfg.headers as Record<string, string>
|
|
178
|
-
const authHeader = headers['Authorization'] ?? headers['authorization']
|
|
179
|
-
if (authHeader) {
|
|
180
|
-
// Extract env var from patterns like "Bearer ${SOME_KEY}"
|
|
181
|
-
const envMatch = authHeader.match(/\$\{(\w+)\}/)
|
|
182
|
-
if (envMatch) {
|
|
183
|
-
entry.auth = {
|
|
184
|
-
type: 'bearer',
|
|
185
|
-
envVar: envMatch[1],
|
|
186
|
-
headerTemplate: authHeader.replace(/\$\{\w+\}/, '${value}'),
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Codex-style bearer_token_env_var
|
|
193
|
-
if (cfg.bearer_token_env_var) {
|
|
194
|
-
entry.auth = {
|
|
195
|
-
type: 'bearer',
|
|
196
|
-
envVar: cfg.bearer_token_env_var as string,
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Codex-style env_http_headers
|
|
201
|
-
if (cfg.env_http_headers && typeof cfg.env_http_headers === 'object') {
|
|
202
|
-
const envHeaders = Object.entries(cfg.env_http_headers as Record<string, string>)
|
|
203
|
-
if (envHeaders.length > 0) {
|
|
204
|
-
const [headerName, envVar] = envHeaders[0]
|
|
205
|
-
entry.auth = {
|
|
206
|
-
type: 'header',
|
|
207
|
-
envVar,
|
|
208
|
-
headerName,
|
|
209
|
-
headerTemplate: '${value}',
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
result[name] = entry
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return result
|
|
218
|
-
} catch {
|
|
219
|
-
continue
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return {}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ── Hooks Parsing ───────────────────────────────────────────────
|
|
227
|
-
|
|
228
|
-
// Maps platform-specific hook event names to pluxx schema names
|
|
229
|
-
const HOOK_EVENT_MAP: Record<string, string> = {
|
|
230
|
-
SessionStart: 'sessionStart',
|
|
231
|
-
SessionEnd: 'sessionEnd',
|
|
232
|
-
PreToolUse: 'preToolUse',
|
|
233
|
-
PostToolUse: 'postToolUse',
|
|
234
|
-
BeforeShellExecution: 'beforeShellExecution',
|
|
235
|
-
AfterShellExecution: 'afterShellExecution',
|
|
236
|
-
BeforeMCPExecution: 'beforeMCPExecution',
|
|
237
|
-
AfterMCPExecution: 'afterMCPExecution',
|
|
238
|
-
AfterFileEdit: 'afterFileEdit',
|
|
239
|
-
BeforeReadFile: 'beforeReadFile',
|
|
240
|
-
BeforeSubmitPrompt: 'beforeSubmitPrompt',
|
|
241
|
-
Stop: 'stop',
|
|
242
|
-
// Also handle already-normalized names
|
|
243
|
-
sessionStart: 'sessionStart',
|
|
244
|
-
sessionEnd: 'sessionEnd',
|
|
245
|
-
preToolUse: 'preToolUse',
|
|
246
|
-
postToolUse: 'postToolUse',
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function parseHooks(pluginDir: string, detection: DetectionResult): ParsedHooks {
|
|
250
|
-
const hooksPaths = [
|
|
251
|
-
resolve(pluginDir, '.codex', 'hooks.json'),
|
|
252
|
-
resolve(pluginDir, 'hooks.json'),
|
|
253
|
-
resolve(pluginDir, 'hooks', 'hooks.json'),
|
|
254
|
-
]
|
|
255
|
-
|
|
256
|
-
// Check if manifest references a hooks file
|
|
257
|
-
try {
|
|
258
|
-
const manifest = JSON.parse(readFileSync(detection.manifestPath, 'utf-8'))
|
|
259
|
-
if (manifest.hooks && typeof manifest.hooks === 'string') {
|
|
260
|
-
hooksPaths.unshift(resolve(pluginDir, manifest.hooks))
|
|
261
|
-
}
|
|
262
|
-
} catch {
|
|
263
|
-
// ignore
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
for (const hooksPath of hooksPaths) {
|
|
267
|
-
if (!existsSync(hooksPath)) continue
|
|
268
|
-
try {
|
|
269
|
-
const raw = JSON.parse(readFileSync(hooksPath, 'utf-8'))
|
|
270
|
-
const hooksObj = raw.hooks ?? raw
|
|
271
|
-
if (!hooksObj || typeof hooksObj !== 'object') continue
|
|
272
|
-
|
|
273
|
-
const result: ParsedHooks = {}
|
|
274
|
-
|
|
275
|
-
for (const [event, entries] of Object.entries(hooksObj)) {
|
|
276
|
-
const normalizedEvent = HOOK_EVENT_MAP[event] ?? event
|
|
277
|
-
const hookEntries: ParsedHooks[string] = []
|
|
278
|
-
|
|
279
|
-
if (!Array.isArray(entries)) continue
|
|
280
|
-
|
|
281
|
-
for (const entry of entries) {
|
|
282
|
-
// Claude Code format: { hooks: [{ type: 'command', command: '...' }] }
|
|
283
|
-
if (entry.hooks && Array.isArray(entry.hooks)) {
|
|
284
|
-
for (const hook of entry.hooks) {
|
|
285
|
-
if (hook.command) {
|
|
286
|
-
hookEntries.push({
|
|
287
|
-
command: hook.command,
|
|
288
|
-
...(hook.timeout && { timeout: hook.timeout }),
|
|
289
|
-
})
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
// Direct format: { command: '...' }
|
|
294
|
-
else if (entry.command) {
|
|
295
|
-
hookEntries.push({
|
|
296
|
-
command: entry.command,
|
|
297
|
-
...(entry.timeout && { timeout: entry.timeout }),
|
|
298
|
-
...(entry.matcher && { matcher: entry.matcher }),
|
|
299
|
-
})
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (hookEntries.length > 0) {
|
|
304
|
-
result[normalizedEvent] = hookEntries
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return result
|
|
309
|
-
} catch {
|
|
310
|
-
continue
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return {}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// ── Instructions Detection ──────────────────────────────────────
|
|
318
|
-
|
|
319
|
-
function findInstructions(pluginDir: string): string | undefined {
|
|
320
|
-
const candidates = [
|
|
321
|
-
'CLAUDE.md',
|
|
322
|
-
'AGENTS.md',
|
|
323
|
-
'instructions.md',
|
|
324
|
-
'INSTRUCTIONS.md',
|
|
325
|
-
'README.md',
|
|
326
|
-
]
|
|
327
|
-
|
|
328
|
-
for (const candidate of candidates) {
|
|
329
|
-
const filePath = resolve(pluginDir, candidate)
|
|
330
|
-
if (existsSync(filePath)) {
|
|
331
|
-
return `./${candidate}`
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return undefined
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// ── Directory Detection ─────────────────────────────────────────
|
|
339
|
-
|
|
340
|
-
function detectDirectories(pluginDir: string) {
|
|
341
|
-
return {
|
|
342
|
-
skills: existsSync(resolve(pluginDir, 'skills')),
|
|
343
|
-
commands: existsSync(resolve(pluginDir, 'commands')),
|
|
344
|
-
agents: existsSync(resolve(pluginDir, 'agents')),
|
|
345
|
-
scripts: existsSync(resolve(pluginDir, 'scripts')),
|
|
346
|
-
assets: existsSync(resolve(pluginDir, 'assets')),
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// ── Copy Directories ────────────────────────────────────────────
|
|
351
|
-
|
|
352
|
-
function copyDirectories(
|
|
353
|
-
pluginDir: string,
|
|
354
|
-
outputDir: string,
|
|
355
|
-
dirs: MigrateResult['directories'],
|
|
356
|
-
): string[] {
|
|
357
|
-
const copied: string[] = []
|
|
358
|
-
const toCopy = ['skills', 'commands', 'agents', 'scripts', 'assets'] as const
|
|
359
|
-
|
|
360
|
-
for (const dir of toCopy) {
|
|
361
|
-
if (!dirs[dir]) continue
|
|
362
|
-
const src = resolve(pluginDir, dir)
|
|
363
|
-
const dest = resolve(outputDir, dir)
|
|
364
|
-
if (existsSync(dest)) {
|
|
365
|
-
console.log(` skip ./${dir}/ (already exists)`)
|
|
366
|
-
continue
|
|
367
|
-
}
|
|
368
|
-
cpSync(src, dest, { recursive: true })
|
|
369
|
-
copied.push(dir)
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return copied
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// ── Config Generation ───────────────────────────────────────────
|
|
376
|
-
|
|
377
|
-
function generateConfigTs(result: MigrateResult): string {
|
|
378
|
-
const lines: string[] = []
|
|
379
|
-
lines.push(`import { definePlugin } from 'pluxx'`)
|
|
380
|
-
lines.push('')
|
|
381
|
-
lines.push('export default definePlugin({')
|
|
382
|
-
|
|
383
|
-
// Identity
|
|
384
|
-
const name = result.manifest.name ?? 'my-plugin'
|
|
385
|
-
lines.push(` name: ${quote(name)},`)
|
|
386
|
-
if (result.manifest.version) {
|
|
387
|
-
lines.push(` version: ${quote(result.manifest.version)},`)
|
|
388
|
-
}
|
|
389
|
-
lines.push(` description: ${quote(result.manifest.description ?? 'TODO: Describe your plugin')},`)
|
|
390
|
-
|
|
391
|
-
// Author
|
|
392
|
-
if (result.manifest.author) {
|
|
393
|
-
const a = result.manifest.author
|
|
394
|
-
lines.push(' author: {')
|
|
395
|
-
lines.push(` name: ${quote(a.name)},`)
|
|
396
|
-
if (a.url) lines.push(` url: ${quote(a.url)},`)
|
|
397
|
-
if (a.email) lines.push(` email: ${quote(a.email)},`)
|
|
398
|
-
lines.push(' },')
|
|
399
|
-
} else {
|
|
400
|
-
lines.push(' author: { name: \'TODO: Your Name\' },')
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
if (result.manifest.repository) {
|
|
404
|
-
lines.push(` repository: ${quote(result.manifest.repository)},`)
|
|
405
|
-
}
|
|
406
|
-
if (result.manifest.license) {
|
|
407
|
-
lines.push(` license: ${quote(result.manifest.license)},`)
|
|
408
|
-
}
|
|
409
|
-
if (result.manifest.keywords && result.manifest.keywords.length > 0) {
|
|
410
|
-
lines.push(` keywords: [${result.manifest.keywords.map(k => quote(k)).join(', ')}],`)
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
lines.push('')
|
|
414
|
-
|
|
415
|
-
// Directories
|
|
416
|
-
if (result.directories.skills) {
|
|
417
|
-
lines.push(` skills: './skills/',`)
|
|
418
|
-
}
|
|
419
|
-
if (result.directories.commands) {
|
|
420
|
-
lines.push(` commands: './commands/',`)
|
|
421
|
-
}
|
|
422
|
-
if (result.directories.agents) {
|
|
423
|
-
lines.push(` agents: './agents/',`)
|
|
424
|
-
}
|
|
425
|
-
if (result.directories.scripts) {
|
|
426
|
-
lines.push(` scripts: './scripts/',`)
|
|
427
|
-
}
|
|
428
|
-
if (result.directories.assets) {
|
|
429
|
-
lines.push(` assets: './assets/',`)
|
|
430
|
-
}
|
|
431
|
-
if (result.instructions) {
|
|
432
|
-
lines.push(` instructions: ${quote(result.instructions)},`)
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// MCP
|
|
436
|
-
const mcpNames = Object.keys(result.mcp)
|
|
437
|
-
if (mcpNames.length > 0) {
|
|
438
|
-
lines.push('')
|
|
439
|
-
lines.push(' mcp: {')
|
|
440
|
-
for (const name of mcpNames) {
|
|
441
|
-
const server = result.mcp[name]
|
|
442
|
-
lines.push(` ${quote(name)}: {`)
|
|
443
|
-
if (server.url) lines.push(` url: ${quote(server.url)},`)
|
|
444
|
-
if (server.transport && server.transport !== 'http') {
|
|
445
|
-
lines.push(` transport: ${quote(server.transport)},`)
|
|
446
|
-
}
|
|
447
|
-
if (server.command) lines.push(` command: ${quote(server.command)},`)
|
|
448
|
-
if (server.args && server.args.length > 0) {
|
|
449
|
-
lines.push(` args: [${server.args.map(a => quote(a)).join(', ')}],`)
|
|
450
|
-
}
|
|
451
|
-
if (server.env) {
|
|
452
|
-
lines.push(' env: {')
|
|
453
|
-
for (const [k, v] of Object.entries(server.env)) {
|
|
454
|
-
lines.push(` ${quote(k)}: ${quote(v)},`)
|
|
455
|
-
}
|
|
456
|
-
lines.push(' },')
|
|
457
|
-
}
|
|
458
|
-
if (server.auth) {
|
|
459
|
-
lines.push(' auth: {')
|
|
460
|
-
lines.push(` type: ${quote(server.auth.type)},`)
|
|
461
|
-
if (server.auth.envVar) lines.push(` envVar: ${quote(server.auth.envVar)},`)
|
|
462
|
-
if (server.auth.headerName && server.auth.headerName !== 'Authorization') {
|
|
463
|
-
lines.push(` headerName: ${quote(server.auth.headerName)},`)
|
|
464
|
-
}
|
|
465
|
-
if (server.auth.headerTemplate && server.auth.headerTemplate !== 'Bearer ${value}') {
|
|
466
|
-
lines.push(` headerTemplate: ${quote(server.auth.headerTemplate)},`)
|
|
467
|
-
}
|
|
468
|
-
lines.push(' },')
|
|
469
|
-
}
|
|
470
|
-
lines.push(' },')
|
|
471
|
-
}
|
|
472
|
-
lines.push(' },')
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// Hooks
|
|
476
|
-
const hookEvents = Object.keys(result.hooks)
|
|
477
|
-
if (hookEvents.length > 0) {
|
|
478
|
-
lines.push('')
|
|
479
|
-
lines.push(' hooks: {')
|
|
480
|
-
for (const event of hookEvents) {
|
|
481
|
-
const entries = result.hooks[event]
|
|
482
|
-
lines.push(` ${event}: [`)
|
|
483
|
-
for (const entry of entries) {
|
|
484
|
-
const parts: string[] = [`command: ${quote(entry.command)}`]
|
|
485
|
-
if (entry.timeout) parts.push(`timeout: ${entry.timeout}`)
|
|
486
|
-
if (entry.matcher) parts.push(`matcher: ${quote(entry.matcher)}`)
|
|
487
|
-
lines.push(` { ${parts.join(', ')} },`)
|
|
488
|
-
}
|
|
489
|
-
lines.push(' ],')
|
|
490
|
-
}
|
|
491
|
-
lines.push(' },')
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
lines.push('')
|
|
495
|
-
lines.push(` // Migrated from ${result.platform} plugin`)
|
|
496
|
-
lines.push(` targets: ['claude-code', 'cursor', 'codex', 'opencode'],`)
|
|
497
|
-
lines.push('})')
|
|
498
|
-
lines.push('')
|
|
499
|
-
|
|
500
|
-
return lines.join('\n')
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
function quote(s: string): string {
|
|
504
|
-
// Use single quotes, escape single quotes inside
|
|
505
|
-
return `'${s.replace(/'/g, "\\'")}'`
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// ── Main Migrate Function ───────────────────────────────────────
|
|
509
|
-
|
|
510
|
-
export async function migrate(inputPath: string): Promise<void> {
|
|
511
|
-
const pluginDir = resolve(inputPath)
|
|
512
|
-
const outputDir = process.cwd()
|
|
513
|
-
|
|
514
|
-
if (!existsSync(pluginDir)) {
|
|
515
|
-
console.error(`Error: Path does not exist: ${pluginDir}`)
|
|
516
|
-
process.exit(1)
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
console.log(`Scanning ${pluginDir} ...`)
|
|
520
|
-
|
|
521
|
-
// 1. Detect platform
|
|
522
|
-
const detection = detectPlatform(pluginDir)
|
|
523
|
-
if (!detection) {
|
|
524
|
-
console.error('Error: Could not detect plugin platform.')
|
|
525
|
-
console.error('Expected one of:')
|
|
526
|
-
console.error(' .claude-plugin/plugin.json')
|
|
527
|
-
console.error(' .cursor-plugin/plugin.json')
|
|
528
|
-
console.error(' .codex-plugin/plugin.json')
|
|
529
|
-
console.error(' package.json with @opencode-ai/plugin dependency')
|
|
530
|
-
process.exit(1)
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
console.log(`Detected: ${detection.platform} plugin`)
|
|
534
|
-
|
|
535
|
-
// 2. Parse manifest
|
|
536
|
-
const manifest = parseManifest(detection)
|
|
537
|
-
console.log(` name: ${manifest.name ?? '(none)'}`)
|
|
538
|
-
console.log(` version: ${manifest.version ?? '(none)'}`)
|
|
539
|
-
|
|
540
|
-
// 3. Parse MCP
|
|
541
|
-
const mcp = parseMcp(pluginDir, detection)
|
|
542
|
-
const mcpCount = Object.keys(mcp).length
|
|
543
|
-
if (mcpCount > 0) {
|
|
544
|
-
console.log(` mcp servers: ${Object.keys(mcp).join(', ')}`)
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// 4. Parse hooks
|
|
548
|
-
const hooks = parseHooks(pluginDir, detection)
|
|
549
|
-
const hookCount = Object.keys(hooks).length
|
|
550
|
-
if (hookCount > 0) {
|
|
551
|
-
console.log(` hooks: ${Object.keys(hooks).join(', ')}`)
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// 5. Find instructions
|
|
555
|
-
const instructions = findInstructions(pluginDir)
|
|
556
|
-
if (instructions) {
|
|
557
|
-
console.log(` instructions: ${instructions}`)
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// 6. Detect directories
|
|
561
|
-
const directories = detectDirectories(pluginDir)
|
|
562
|
-
const dirNames = Object.entries(directories)
|
|
563
|
-
.filter(([_, exists]) => exists)
|
|
564
|
-
.map(([name]) => name)
|
|
565
|
-
if (dirNames.length > 0) {
|
|
566
|
-
console.log(` directories: ${dirNames.join(', ')}`)
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// 7. Build result
|
|
570
|
-
const result: MigrateResult = {
|
|
571
|
-
platform: detection.platform,
|
|
572
|
-
manifest,
|
|
573
|
-
mcp,
|
|
574
|
-
hooks,
|
|
575
|
-
instructions,
|
|
576
|
-
directories,
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// 8. Generate config
|
|
580
|
-
const configContent = generateConfigTs(result)
|
|
581
|
-
const configPath = resolve(outputDir, 'pluxx.config.ts')
|
|
582
|
-
|
|
583
|
-
if (existsSync(configPath)) {
|
|
584
|
-
console.error(`\nError: pluxx.config.ts already exists in ${outputDir}`)
|
|
585
|
-
console.error('Remove it first or run from a different directory.')
|
|
586
|
-
process.exit(1)
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
await Bun.write(configPath, configContent)
|
|
590
|
-
console.log(`\nGenerated pluxx.config.ts`)
|
|
591
|
-
|
|
592
|
-
// 9. Copy directories
|
|
593
|
-
const copied = copyDirectories(pluginDir, outputDir, directories)
|
|
594
|
-
if (copied.length > 0) {
|
|
595
|
-
console.log(`Copied: ${copied.map(d => `./${d}/`).join(', ')}`)
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// 10. Copy instructions file if it exists and is not README.md
|
|
599
|
-
if (instructions && instructions !== './README.md') {
|
|
600
|
-
const srcInstr = resolve(pluginDir, instructions)
|
|
601
|
-
const destInstr = resolve(outputDir, instructions)
|
|
602
|
-
if (!existsSync(destInstr)) {
|
|
603
|
-
const content = readFileSync(srcInstr, 'utf-8')
|
|
604
|
-
await Bun.write(destInstr, content)
|
|
605
|
-
console.log(`Copied: ${instructions}`)
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
console.log('')
|
|
610
|
-
console.log('Migration complete! Next steps:')
|
|
611
|
-
console.log(' 1. Review pluxx.config.ts and fill in any TODOs')
|
|
612
|
-
console.log(' 2. Run: pluxx validate')
|
|
613
|
-
console.log(' 3. Run: pluxx build')
|
|
614
|
-
}
|
package/src/cli/prompt.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Simple interactive prompt utilities using Bun's built-in readline.
|
|
3
|
-
* Zero external dependencies.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as readline from 'readline'
|
|
7
|
-
|
|
8
|
-
export class PromptCancelledError extends Error {
|
|
9
|
-
constructor() {
|
|
10
|
-
super('Init cancelled')
|
|
11
|
-
this.name = 'PromptCancelledError'
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function ask(question: string): Promise<string> {
|
|
16
|
-
return new Promise((resolve, reject) => {
|
|
17
|
-
const rl = readline.createInterface({
|
|
18
|
-
input: process.stdin,
|
|
19
|
-
output: process.stdout,
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
let settled = false
|
|
23
|
-
|
|
24
|
-
const settle = (fn: () => void) => {
|
|
25
|
-
if (settled) return
|
|
26
|
-
settled = true
|
|
27
|
-
rl.removeListener('SIGINT', onCancel)
|
|
28
|
-
rl.removeListener('close', onClose)
|
|
29
|
-
fn()
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const onCancel = () => {
|
|
33
|
-
settle(() => {
|
|
34
|
-
rl.close()
|
|
35
|
-
reject(new PromptCancelledError())
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const onClose = () => {
|
|
40
|
-
settle(() => {
|
|
41
|
-
reject(new PromptCancelledError())
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
rl.once('SIGINT', onCancel)
|
|
46
|
-
rl.once('close', onClose)
|
|
47
|
-
|
|
48
|
-
rl.question(question, (answer) => {
|
|
49
|
-
settle(() => {
|
|
50
|
-
resolve(answer)
|
|
51
|
-
rl.close()
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Prompt for text input with an optional default value.
|
|
59
|
-
*/
|
|
60
|
-
export async function promptText(label: string, defaultValue?: string): Promise<string> {
|
|
61
|
-
const suffix = defaultValue ? ` (${defaultValue})` : ''
|
|
62
|
-
const answer = await ask(` ${label}${suffix}: `)
|
|
63
|
-
return answer.trim() || defaultValue || ''
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Prompt for a yes/no answer. Returns true for yes.
|
|
68
|
-
*/
|
|
69
|
-
export async function promptYesNo(label: string, defaultYes = false): Promise<boolean> {
|
|
70
|
-
const hint = defaultYes ? '(Y/n)' : '(y/N)'
|
|
71
|
-
const answer = await ask(` ${label} ${hint}: `)
|
|
72
|
-
const normalized = answer.trim().toLowerCase()
|
|
73
|
-
if (normalized === '') return defaultYes
|
|
74
|
-
return normalized === 'y' || normalized === 'yes'
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Close the readline interface. Must be called when done prompting.
|
|
79
|
-
*/
|
|
80
|
-
export function closePrompts(): void {
|
|
81
|
-
// Prompts are created per-question, so there is nothing to close here.
|
|
82
|
-
}
|