@orchid-labs/pluxx 0.1.1 → 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 +25 -8
- 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 +62 -0
- package/dist/cli/agent.d.ts.map +1 -1
- package/dist/cli/doctor.d.ts +2 -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/index.d.ts +7 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +21810 -0
- package/dist/cli/init-from-mcp.d.ts +17 -1
- package/dist/cli/init-from-mcp.d.ts.map +1 -1
- package/dist/cli/install.d.ts +1 -0
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/lint.d.ts +3 -1
- package/dist/cli/lint.d.ts.map +1 -1
- package/dist/cli/mcp-proxy.d.ts.map +1 -1
- 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/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.map +1 -1
- package/dist/generators/cline/index.d.ts.map +1 -1
- package/dist/generators/codex/index.d.ts +4 -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 +5371 -553
- 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 +15 -1
- package/dist/validation/platform-rules.d.ts.map +1 -1
- package/package.json +15 -13
- package/src/cli/agent.ts +0 -1455
- package/src/cli/dev.ts +0 -112
- package/src/cli/doctor.ts +0 -987
- package/src/cli/eval.ts +0 -470
- package/src/cli/index.ts +0 -2933
- package/src/cli/init-from-mcp.ts +0 -2115
- package/src/cli/install.ts +0 -860
- package/src/cli/lint.ts +0 -1249
- package/src/cli/mcp-proxy.ts +0 -322
- package/src/cli/migrate.ts +0 -867
- 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 -586
- package/src/cli/test.ts +0 -142
- 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 -172
- package/src/generators/cline/index.ts +0 -35
- package/src/generators/codex/index.ts +0 -143
- 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 -34
- package/src/mcp/introspect.ts +0 -1107
- package/src/permissions.ts +0 -260
- package/src/schema.ts +0 -312
- package/src/user-config.ts +0 -177
- package/src/validation/platform-rules.ts +0 -686
package/src/cli/migrate.ts
DELETED
|
@@ -1,867 +0,0 @@
|
|
|
1
|
-
import { resolve, basename } from 'path'
|
|
2
|
-
import { existsSync, readdirSync, mkdirSync, cpSync, readFileSync } from 'fs'
|
|
3
|
-
import {
|
|
4
|
-
MCP_SCAFFOLD_METADATA_PATH,
|
|
5
|
-
MCP_TAXONOMY_PATH,
|
|
6
|
-
type McpScaffoldMetadata,
|
|
7
|
-
type PersistedSkill,
|
|
8
|
-
} from './init-from-mcp'
|
|
9
|
-
import type { McpServer } from '../schema'
|
|
10
|
-
|
|
11
|
-
type DetectedPlatform = 'claude-code' | 'cursor' | 'codex' | 'opencode'
|
|
12
|
-
|
|
13
|
-
interface DetectionResult {
|
|
14
|
-
platform: DetectedPlatform
|
|
15
|
-
manifestPath: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface ParsedManifest {
|
|
19
|
-
name?: string
|
|
20
|
-
version?: string
|
|
21
|
-
description?: string
|
|
22
|
-
author?: { name: string; url?: string; email?: string }
|
|
23
|
-
repository?: string
|
|
24
|
-
license?: string
|
|
25
|
-
keywords?: string[]
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface ParsedMcp {
|
|
29
|
-
[serverName: string]: {
|
|
30
|
-
url?: string
|
|
31
|
-
transport?: 'http' | 'sse' | 'stdio'
|
|
32
|
-
command?: string
|
|
33
|
-
args?: string[]
|
|
34
|
-
env?: Record<string, string>
|
|
35
|
-
auth?: {
|
|
36
|
-
type: 'bearer' | 'header' | 'none'
|
|
37
|
-
envVar?: string
|
|
38
|
-
headerName?: string
|
|
39
|
-
headerTemplate?: string
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface ParsedHooks {
|
|
45
|
-
[event: string]: Array<{
|
|
46
|
-
command: string
|
|
47
|
-
timeout?: number
|
|
48
|
-
matcher?: string
|
|
49
|
-
}>
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface MigrateResult {
|
|
53
|
-
platform: DetectedPlatform
|
|
54
|
-
manifest: ParsedManifest
|
|
55
|
-
mcp: ParsedMcp
|
|
56
|
-
hooks: ParsedHooks
|
|
57
|
-
instructions?: string
|
|
58
|
-
directories: {
|
|
59
|
-
skills: boolean
|
|
60
|
-
commands: boolean
|
|
61
|
-
agents: boolean
|
|
62
|
-
scripts: boolean
|
|
63
|
-
assets: boolean
|
|
64
|
-
}
|
|
65
|
-
persistedSkills: PersistedSkill[]
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ── Platform Detection ──────────────────────────────────────────
|
|
69
|
-
|
|
70
|
-
function detectPlatform(pluginDir: string): DetectionResult | null {
|
|
71
|
-
const checks: Array<{ dir: string; platform: DetectedPlatform }> = [
|
|
72
|
-
{ dir: '.claude-plugin', platform: 'claude-code' },
|
|
73
|
-
{ dir: '.cursor-plugin', platform: 'cursor' },
|
|
74
|
-
{ dir: '.codex-plugin', platform: 'codex' },
|
|
75
|
-
]
|
|
76
|
-
|
|
77
|
-
for (const check of checks) {
|
|
78
|
-
const manifestPath = resolve(pluginDir, check.dir, 'plugin.json')
|
|
79
|
-
if (existsSync(manifestPath)) {
|
|
80
|
-
return { platform: check.platform, manifestPath }
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Check for OpenCode (package.json with @opencode-ai/plugin)
|
|
85
|
-
const pkgPath = resolve(pluginDir, 'package.json')
|
|
86
|
-
if (existsSync(pkgPath)) {
|
|
87
|
-
try {
|
|
88
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
|
89
|
-
const deps = {
|
|
90
|
-
...pkg.dependencies,
|
|
91
|
-
...pkg.devDependencies,
|
|
92
|
-
...pkg.peerDependencies,
|
|
93
|
-
}
|
|
94
|
-
if (deps && '@opencode-ai/plugin' in deps) {
|
|
95
|
-
return { platform: 'opencode', manifestPath: pkgPath }
|
|
96
|
-
}
|
|
97
|
-
} catch {
|
|
98
|
-
// not valid JSON, skip
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return null
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ── Manifest Parsing ────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
function parseManifest(detection: DetectionResult): ParsedManifest {
|
|
108
|
-
const raw = JSON.parse(readFileSync(detection.manifestPath, 'utf-8'))
|
|
109
|
-
|
|
110
|
-
const result: ParsedManifest = {}
|
|
111
|
-
|
|
112
|
-
if (raw.name) result.name = raw.name
|
|
113
|
-
if (raw.version) result.version = raw.version
|
|
114
|
-
if (raw.description) result.description = raw.description
|
|
115
|
-
if (raw.license) result.license = raw.license
|
|
116
|
-
if (raw.keywords) result.keywords = raw.keywords
|
|
117
|
-
if (raw.repository) {
|
|
118
|
-
result.repository = typeof raw.repository === 'string'
|
|
119
|
-
? raw.repository
|
|
120
|
-
: raw.repository?.url
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (raw.author) {
|
|
124
|
-
if (typeof raw.author === 'string') {
|
|
125
|
-
result.author = { name: raw.author }
|
|
126
|
-
} else {
|
|
127
|
-
result.author = {
|
|
128
|
-
name: raw.author.name,
|
|
129
|
-
...(raw.author.url && { url: raw.author.url }),
|
|
130
|
-
...(raw.author.email && { email: raw.author.email }),
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return result
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ── MCP Parsing ─────────────────────────────────────────────────
|
|
139
|
-
|
|
140
|
-
function parseMcp(pluginDir: string, detection: DetectionResult): ParsedMcp {
|
|
141
|
-
const mcpPaths = [
|
|
142
|
-
resolve(pluginDir, '.mcp.json'),
|
|
143
|
-
resolve(pluginDir, 'mcp.json'),
|
|
144
|
-
]
|
|
145
|
-
|
|
146
|
-
// Also check if the manifest references an mcpServers file
|
|
147
|
-
try {
|
|
148
|
-
const manifest = JSON.parse(readFileSync(detection.manifestPath, 'utf-8'))
|
|
149
|
-
if (manifest.mcpServers && typeof manifest.mcpServers === 'string') {
|
|
150
|
-
mcpPaths.unshift(resolve(pluginDir, manifest.mcpServers))
|
|
151
|
-
}
|
|
152
|
-
} catch {
|
|
153
|
-
// ignore
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
for (const mcpPath of mcpPaths) {
|
|
157
|
-
if (!existsSync(mcpPath)) continue
|
|
158
|
-
try {
|
|
159
|
-
const raw = JSON.parse(readFileSync(mcpPath, 'utf-8'))
|
|
160
|
-
const servers = raw.mcpServers ?? raw
|
|
161
|
-
if (!servers || typeof servers !== 'object') continue
|
|
162
|
-
|
|
163
|
-
const result: ParsedMcp = {}
|
|
164
|
-
|
|
165
|
-
for (const [name, config] of Object.entries(servers)) {
|
|
166
|
-
const cfg = config as Record<string, unknown>
|
|
167
|
-
const entry: ParsedMcp[string] = {}
|
|
168
|
-
|
|
169
|
-
if (cfg.url) entry.url = cfg.url as string
|
|
170
|
-
|
|
171
|
-
// Detect transport
|
|
172
|
-
if (cfg.type === 'stdio' || cfg.command) {
|
|
173
|
-
entry.transport = 'stdio'
|
|
174
|
-
if (cfg.command) entry.command = cfg.command as string
|
|
175
|
-
if (cfg.args) entry.args = cfg.args as string[]
|
|
176
|
-
if (cfg.env) entry.env = cfg.env as Record<string, string>
|
|
177
|
-
} else if (cfg.type === 'sse') {
|
|
178
|
-
entry.transport = 'sse'
|
|
179
|
-
} else {
|
|
180
|
-
entry.transport = 'http'
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Parse auth from headers
|
|
184
|
-
if (cfg.headers && typeof cfg.headers === 'object') {
|
|
185
|
-
const headers = cfg.headers as Record<string, string>
|
|
186
|
-
const authHeader = headers['Authorization'] ?? headers['authorization']
|
|
187
|
-
if (authHeader) {
|
|
188
|
-
// Extract env var from patterns like "Bearer ${SOME_KEY}"
|
|
189
|
-
const envMatch = authHeader.match(/\$\{(\w+)\}/)
|
|
190
|
-
if (envMatch) {
|
|
191
|
-
entry.auth = {
|
|
192
|
-
type: 'bearer',
|
|
193
|
-
envVar: envMatch[1],
|
|
194
|
-
headerTemplate: authHeader.replace(/\$\{\w+\}/, '${value}'),
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Codex-style bearer_token_env_var
|
|
201
|
-
if (cfg.bearer_token_env_var) {
|
|
202
|
-
entry.auth = {
|
|
203
|
-
type: 'bearer',
|
|
204
|
-
envVar: cfg.bearer_token_env_var as string,
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Codex-style env_http_headers
|
|
209
|
-
if (cfg.env_http_headers && typeof cfg.env_http_headers === 'object') {
|
|
210
|
-
const envHeaders = Object.entries(cfg.env_http_headers as Record<string, string>)
|
|
211
|
-
if (envHeaders.length > 0) {
|
|
212
|
-
const [headerName, envVar] = envHeaders[0]
|
|
213
|
-
entry.auth = {
|
|
214
|
-
type: 'header',
|
|
215
|
-
envVar,
|
|
216
|
-
headerName,
|
|
217
|
-
headerTemplate: '${value}',
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
result[name] = entry
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return result
|
|
226
|
-
} catch {
|
|
227
|
-
continue
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return {}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// ── Hooks Parsing ───────────────────────────────────────────────
|
|
235
|
-
|
|
236
|
-
// Maps platform-specific hook event names to pluxx schema names
|
|
237
|
-
const HOOK_EVENT_MAP: Record<string, string> = {
|
|
238
|
-
SessionStart: 'sessionStart',
|
|
239
|
-
SessionEnd: 'sessionEnd',
|
|
240
|
-
PreToolUse: 'preToolUse',
|
|
241
|
-
PostToolUse: 'postToolUse',
|
|
242
|
-
BeforeShellExecution: 'beforeShellExecution',
|
|
243
|
-
AfterShellExecution: 'afterShellExecution',
|
|
244
|
-
BeforeMCPExecution: 'beforeMCPExecution',
|
|
245
|
-
AfterMCPExecution: 'afterMCPExecution',
|
|
246
|
-
AfterFileEdit: 'afterFileEdit',
|
|
247
|
-
BeforeReadFile: 'beforeReadFile',
|
|
248
|
-
BeforeSubmitPrompt: 'beforeSubmitPrompt',
|
|
249
|
-
Stop: 'stop',
|
|
250
|
-
// Also handle already-normalized names
|
|
251
|
-
sessionStart: 'sessionStart',
|
|
252
|
-
sessionEnd: 'sessionEnd',
|
|
253
|
-
preToolUse: 'preToolUse',
|
|
254
|
-
postToolUse: 'postToolUse',
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function parseHooks(pluginDir: string, detection: DetectionResult): ParsedHooks {
|
|
258
|
-
const hooksPaths = [
|
|
259
|
-
resolve(pluginDir, '.codex', 'hooks.json'),
|
|
260
|
-
resolve(pluginDir, 'hooks.json'),
|
|
261
|
-
resolve(pluginDir, 'hooks', 'hooks.json'),
|
|
262
|
-
]
|
|
263
|
-
|
|
264
|
-
// Check if manifest references a hooks file
|
|
265
|
-
try {
|
|
266
|
-
const manifest = JSON.parse(readFileSync(detection.manifestPath, 'utf-8'))
|
|
267
|
-
if (manifest.hooks && typeof manifest.hooks === 'string') {
|
|
268
|
-
hooksPaths.unshift(resolve(pluginDir, manifest.hooks))
|
|
269
|
-
}
|
|
270
|
-
} catch {
|
|
271
|
-
// ignore
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
for (const hooksPath of hooksPaths) {
|
|
275
|
-
if (!existsSync(hooksPath)) continue
|
|
276
|
-
try {
|
|
277
|
-
const raw = JSON.parse(readFileSync(hooksPath, 'utf-8'))
|
|
278
|
-
const hooksObj = raw.hooks ?? raw
|
|
279
|
-
if (!hooksObj || typeof hooksObj !== 'object') continue
|
|
280
|
-
|
|
281
|
-
const result: ParsedHooks = {}
|
|
282
|
-
|
|
283
|
-
for (const [event, entries] of Object.entries(hooksObj)) {
|
|
284
|
-
const normalizedEvent = HOOK_EVENT_MAP[event] ?? event
|
|
285
|
-
const hookEntries: ParsedHooks[string] = []
|
|
286
|
-
|
|
287
|
-
if (!Array.isArray(entries)) continue
|
|
288
|
-
|
|
289
|
-
for (const entry of entries) {
|
|
290
|
-
// Claude Code format: { hooks: [{ type: 'command', command: '...' }] }
|
|
291
|
-
if (entry.hooks && Array.isArray(entry.hooks)) {
|
|
292
|
-
for (const hook of entry.hooks) {
|
|
293
|
-
if (hook.command) {
|
|
294
|
-
hookEntries.push({
|
|
295
|
-
command: hook.command,
|
|
296
|
-
...(hook.timeout && { timeout: hook.timeout }),
|
|
297
|
-
})
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
// Direct format: { command: '...' }
|
|
302
|
-
else if (entry.command) {
|
|
303
|
-
hookEntries.push({
|
|
304
|
-
command: entry.command,
|
|
305
|
-
...(entry.timeout && { timeout: entry.timeout }),
|
|
306
|
-
...(entry.matcher && { matcher: entry.matcher }),
|
|
307
|
-
})
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (hookEntries.length > 0) {
|
|
312
|
-
result[normalizedEvent] = hookEntries
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return result
|
|
317
|
-
} catch {
|
|
318
|
-
continue
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return {}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// ── Instructions Detection ──────────────────────────────────────
|
|
326
|
-
|
|
327
|
-
function findInstructions(pluginDir: string): string | undefined {
|
|
328
|
-
const candidates = [
|
|
329
|
-
'CLAUDE.md',
|
|
330
|
-
'AGENTS.md',
|
|
331
|
-
'instructions.md',
|
|
332
|
-
'INSTRUCTIONS.md',
|
|
333
|
-
'README.md',
|
|
334
|
-
]
|
|
335
|
-
|
|
336
|
-
for (const candidate of candidates) {
|
|
337
|
-
const filePath = resolve(pluginDir, candidate)
|
|
338
|
-
if (existsSync(filePath)) {
|
|
339
|
-
return `./${candidate}`
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
return undefined
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// ── Directory Detection ─────────────────────────────────────────
|
|
347
|
-
|
|
348
|
-
function detectDirectories(pluginDir: string) {
|
|
349
|
-
return {
|
|
350
|
-
skills: existsSync(resolve(pluginDir, 'skills')),
|
|
351
|
-
commands: existsSync(resolve(pluginDir, 'commands')),
|
|
352
|
-
agents: existsSync(resolve(pluginDir, 'agents')),
|
|
353
|
-
scripts: existsSync(resolve(pluginDir, 'scripts')),
|
|
354
|
-
assets: existsSync(resolve(pluginDir, 'assets')),
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function toKebabCase(value: string): string {
|
|
359
|
-
return value
|
|
360
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
361
|
-
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
362
|
-
.replace(/^-+|-+$/g, '')
|
|
363
|
-
.toLowerCase()
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function titleCaseFromDirName(value: string): string {
|
|
367
|
-
return value
|
|
368
|
-
.split(/[-_]+/)
|
|
369
|
-
.filter(Boolean)
|
|
370
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
371
|
-
.join(' ')
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function firstHeading(content: string): string | undefined {
|
|
375
|
-
for (const line of content.split(/\r?\n/)) {
|
|
376
|
-
const trimmed = line.trim()
|
|
377
|
-
const match = trimmed.match(/^#\s+(.+)$/)
|
|
378
|
-
if (match) {
|
|
379
|
-
return match[1].trim()
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
return undefined
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function extractFrontmatterField(content: string, key: 'name' | 'description'): string | undefined {
|
|
386
|
-
const lines = content.split(/\r?\n/)
|
|
387
|
-
if (lines[0]?.trim() !== '---') return undefined
|
|
388
|
-
|
|
389
|
-
let endIndex = -1
|
|
390
|
-
for (let i = 1; i < lines.length; i += 1) {
|
|
391
|
-
if (lines[i].trim() === '---') {
|
|
392
|
-
endIndex = i
|
|
393
|
-
break
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (endIndex === -1) return undefined
|
|
398
|
-
|
|
399
|
-
for (const line of lines.slice(1, endIndex)) {
|
|
400
|
-
const match = line.match(new RegExp(`^${key}:\\s*(.*)$`))
|
|
401
|
-
if (!match) continue
|
|
402
|
-
return match[1].trim().replace(/^['"]|['"]$/g, '')
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
return undefined
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
function readMigratedSkills(pluginDir: string, dirs: MigrateResult['directories']): PersistedSkill[] {
|
|
409
|
-
const skills: PersistedSkill[] = []
|
|
410
|
-
|
|
411
|
-
if (dirs.skills) {
|
|
412
|
-
const skillsDir = resolve(pluginDir, 'skills')
|
|
413
|
-
const entries = readdirSync(skillsDir, { withFileTypes: true })
|
|
414
|
-
|
|
415
|
-
for (const entry of entries) {
|
|
416
|
-
if (!entry.isDirectory()) continue
|
|
417
|
-
const dirName = entry.name
|
|
418
|
-
const skillPath = resolve(skillsDir, dirName, 'SKILL.md')
|
|
419
|
-
let title = titleCaseFromDirName(dirName)
|
|
420
|
-
let description: string | undefined
|
|
421
|
-
|
|
422
|
-
if (existsSync(skillPath)) {
|
|
423
|
-
const content = readFileSync(skillPath, 'utf-8')
|
|
424
|
-
title = extractFrontmatterField(content, 'name')
|
|
425
|
-
?? firstHeading(content)
|
|
426
|
-
?? title
|
|
427
|
-
description = extractFrontmatterField(content, 'description')
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
skills.push({
|
|
431
|
-
dirName,
|
|
432
|
-
title,
|
|
433
|
-
description,
|
|
434
|
-
toolNames: [],
|
|
435
|
-
})
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (skills.length === 0 && dirs.commands) {
|
|
440
|
-
const commandsDir = resolve(pluginDir, 'commands')
|
|
441
|
-
const entries = readdirSync(commandsDir, { withFileTypes: true })
|
|
442
|
-
|
|
443
|
-
for (const entry of entries) {
|
|
444
|
-
if (!entry.isFile() || !entry.name.endsWith('.md')) continue
|
|
445
|
-
const dirName = toKebabCase(entry.name.replace(/\.md$/, '')) || 'command'
|
|
446
|
-
const content = readFileSync(resolve(commandsDir, entry.name), 'utf-8')
|
|
447
|
-
skills.push({
|
|
448
|
-
dirName,
|
|
449
|
-
title: firstHeading(content) ?? titleCaseFromDirName(dirName),
|
|
450
|
-
description: extractFrontmatterField(content, 'description'),
|
|
451
|
-
toolNames: [],
|
|
452
|
-
})
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
return skills.sort((a, b) => a.dirName.localeCompare(b.dirName))
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function primarySource(result: MigrateResult): McpServer {
|
|
460
|
-
const [serverName, server] = Object.entries(result.mcp)[0] ?? []
|
|
461
|
-
const auth = normalizeMigrateAuth(server?.auth)
|
|
462
|
-
if (server) {
|
|
463
|
-
if (server.transport === 'stdio') {
|
|
464
|
-
return {
|
|
465
|
-
transport: 'stdio',
|
|
466
|
-
command: server.command ?? 'TODO_MCP_COMMAND',
|
|
467
|
-
args: server.args ?? [],
|
|
468
|
-
...(server.env ? { env: server.env } : {}),
|
|
469
|
-
...(auth ? { auth } : {}),
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
if (server.transport === 'sse') {
|
|
474
|
-
return {
|
|
475
|
-
transport: 'sse',
|
|
476
|
-
url: server.url ?? `https://example.com/${serverName ?? 'mcp'}`,
|
|
477
|
-
...(auth ? { auth } : {}),
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
return {
|
|
482
|
-
transport: 'http',
|
|
483
|
-
url: server.url ?? `https://example.com/${serverName ?? 'mcp'}`,
|
|
484
|
-
...(auth ? { auth } : {}),
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return {
|
|
489
|
-
transport: 'stdio',
|
|
490
|
-
command: 'TODO_MCP_COMMAND',
|
|
491
|
-
args: [],
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function normalizeMigrateAuth(auth: ParsedMcp[string]['auth']) {
|
|
496
|
-
if (!auth) return undefined
|
|
497
|
-
if (auth.type === 'none') {
|
|
498
|
-
return { type: 'none' as const }
|
|
499
|
-
}
|
|
500
|
-
if (auth.type === 'bearer' && auth.envVar) {
|
|
501
|
-
return {
|
|
502
|
-
type: 'bearer' as const,
|
|
503
|
-
envVar: auth.envVar,
|
|
504
|
-
headerName: auth.headerName ?? 'Authorization',
|
|
505
|
-
headerTemplate: auth.headerTemplate ?? 'Bearer ${value}',
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
if (auth.type === 'header' && auth.envVar && auth.headerName) {
|
|
509
|
-
return {
|
|
510
|
-
type: 'header' as const,
|
|
511
|
-
envVar: auth.envVar,
|
|
512
|
-
headerName: auth.headerName,
|
|
513
|
-
headerTemplate: auth.headerTemplate ?? '${value}',
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
return undefined
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function buildMigratedScaffoldMetadata(result: MigrateResult, outputDir: string): McpScaffoldMetadata {
|
|
520
|
-
const pluginName = result.manifest.name ?? 'my-plugin'
|
|
521
|
-
const displayName = result.manifest.name ? titleCaseFromDirName(result.manifest.name) : 'Migrated Plugin'
|
|
522
|
-
const description = result.manifest.description ?? 'Migrated plugin scaffold.'
|
|
523
|
-
const generatedHookEvents = Object.keys(result.hooks)
|
|
524
|
-
const managedFiles = [
|
|
525
|
-
...(result.instructions ? [result.instructions.replace(/^\.\//, '')] : []),
|
|
526
|
-
...(['skills', 'commands', 'agents', 'scripts', 'assets'] as const).flatMap((dir) => {
|
|
527
|
-
if (!result.directories[dir]) return []
|
|
528
|
-
const baseDir = dir
|
|
529
|
-
const dirPath = resolve(outputDir, baseDir)
|
|
530
|
-
if (!existsSync(dirPath)) return []
|
|
531
|
-
const entries = readdirSync(dirPath, { withFileTypes: true })
|
|
532
|
-
const files: string[] = []
|
|
533
|
-
for (const entry of entries) {
|
|
534
|
-
if (entry.isDirectory()) {
|
|
535
|
-
const nestedDir = resolve(dirPath, entry.name)
|
|
536
|
-
for (const nested of readdirSync(nestedDir, { withFileTypes: true })) {
|
|
537
|
-
if (nested.isFile()) {
|
|
538
|
-
files.push(`${baseDir}/${entry.name}/${nested.name}`)
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
continue
|
|
542
|
-
}
|
|
543
|
-
if (entry.isFile()) {
|
|
544
|
-
files.push(`${baseDir}/${entry.name}`)
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
return files
|
|
548
|
-
}),
|
|
549
|
-
'pluxx.config.ts',
|
|
550
|
-
MCP_TAXONOMY_PATH,
|
|
551
|
-
MCP_SCAFFOLD_METADATA_PATH,
|
|
552
|
-
]
|
|
553
|
-
|
|
554
|
-
return {
|
|
555
|
-
version: 1,
|
|
556
|
-
source: primarySource(result),
|
|
557
|
-
serverInfo: {
|
|
558
|
-
name: pluginName,
|
|
559
|
-
title: displayName,
|
|
560
|
-
version: result.manifest.version ?? '0.1.0',
|
|
561
|
-
description,
|
|
562
|
-
...(result.manifest.repository ? { websiteUrl: result.manifest.repository } : {}),
|
|
563
|
-
},
|
|
564
|
-
settings: {
|
|
565
|
-
pluginName,
|
|
566
|
-
displayName,
|
|
567
|
-
description,
|
|
568
|
-
skillGrouping: 'workflow',
|
|
569
|
-
requestedHookMode: generatedHookEvents.length > 0 ? 'safe' : 'none',
|
|
570
|
-
generatedHookMode: generatedHookEvents.length > 0 ? 'safe' : 'none',
|
|
571
|
-
generatedHookEvents,
|
|
572
|
-
runtimeAuthMode: 'inline',
|
|
573
|
-
},
|
|
574
|
-
userConfig: [],
|
|
575
|
-
tools: [],
|
|
576
|
-
resources: [],
|
|
577
|
-
resourceTemplates: [],
|
|
578
|
-
prompts: [],
|
|
579
|
-
skills: result.persistedSkills.map((skill) => ({
|
|
580
|
-
dirName: skill.dirName,
|
|
581
|
-
title: skill.title,
|
|
582
|
-
description: skill.description,
|
|
583
|
-
toolNames: skill.toolNames,
|
|
584
|
-
})),
|
|
585
|
-
managedFiles: [...new Set(managedFiles)].sort(),
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// ── Copy Directories ────────────────────────────────────────────
|
|
590
|
-
|
|
591
|
-
function copyDirectories(
|
|
592
|
-
pluginDir: string,
|
|
593
|
-
outputDir: string,
|
|
594
|
-
dirs: MigrateResult['directories'],
|
|
595
|
-
): string[] {
|
|
596
|
-
const copied: string[] = []
|
|
597
|
-
const toCopy = ['skills', 'commands', 'agents', 'scripts', 'assets'] as const
|
|
598
|
-
|
|
599
|
-
for (const dir of toCopy) {
|
|
600
|
-
if (!dirs[dir]) continue
|
|
601
|
-
const src = resolve(pluginDir, dir)
|
|
602
|
-
const dest = resolve(outputDir, dir)
|
|
603
|
-
if (existsSync(dest)) {
|
|
604
|
-
console.log(` skip ./${dir}/ (already exists)`)
|
|
605
|
-
continue
|
|
606
|
-
}
|
|
607
|
-
cpSync(src, dest, { recursive: true })
|
|
608
|
-
copied.push(dir)
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
return copied
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// ── Config Generation ───────────────────────────────────────────
|
|
615
|
-
|
|
616
|
-
function generateConfigTs(result: MigrateResult): string {
|
|
617
|
-
const lines: string[] = []
|
|
618
|
-
lines.push(`import { definePlugin } from 'pluxx'`)
|
|
619
|
-
lines.push('')
|
|
620
|
-
lines.push('export default definePlugin({')
|
|
621
|
-
|
|
622
|
-
// Identity
|
|
623
|
-
const name = result.manifest.name ?? 'my-plugin'
|
|
624
|
-
lines.push(` name: ${quote(name)},`)
|
|
625
|
-
if (result.manifest.version) {
|
|
626
|
-
lines.push(` version: ${quote(result.manifest.version)},`)
|
|
627
|
-
}
|
|
628
|
-
lines.push(` description: ${quote(result.manifest.description ?? 'TODO: Describe your plugin')},`)
|
|
629
|
-
|
|
630
|
-
// Author
|
|
631
|
-
if (result.manifest.author) {
|
|
632
|
-
const a = result.manifest.author
|
|
633
|
-
lines.push(' author: {')
|
|
634
|
-
lines.push(` name: ${quote(a.name)},`)
|
|
635
|
-
if (a.url) lines.push(` url: ${quote(a.url)},`)
|
|
636
|
-
if (a.email) lines.push(` email: ${quote(a.email)},`)
|
|
637
|
-
lines.push(' },')
|
|
638
|
-
} else {
|
|
639
|
-
lines.push(' author: { name: \'TODO: Your Name\' },')
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
if (result.manifest.repository) {
|
|
643
|
-
lines.push(` repository: ${quote(result.manifest.repository)},`)
|
|
644
|
-
}
|
|
645
|
-
if (result.manifest.license) {
|
|
646
|
-
lines.push(` license: ${quote(result.manifest.license)},`)
|
|
647
|
-
}
|
|
648
|
-
if (result.manifest.keywords && result.manifest.keywords.length > 0) {
|
|
649
|
-
lines.push(` keywords: [${result.manifest.keywords.map(k => quote(k)).join(', ')}],`)
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
lines.push('')
|
|
653
|
-
|
|
654
|
-
// Directories
|
|
655
|
-
if (result.directories.skills) {
|
|
656
|
-
lines.push(` skills: './skills/',`)
|
|
657
|
-
}
|
|
658
|
-
if (result.directories.commands) {
|
|
659
|
-
lines.push(` commands: './commands/',`)
|
|
660
|
-
}
|
|
661
|
-
if (result.directories.agents) {
|
|
662
|
-
lines.push(` agents: './agents/',`)
|
|
663
|
-
}
|
|
664
|
-
if (result.directories.scripts) {
|
|
665
|
-
lines.push(` scripts: './scripts/',`)
|
|
666
|
-
}
|
|
667
|
-
if (result.directories.assets) {
|
|
668
|
-
lines.push(` assets: './assets/',`)
|
|
669
|
-
}
|
|
670
|
-
if (result.instructions) {
|
|
671
|
-
lines.push(` instructions: ${quote(result.instructions)},`)
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// MCP
|
|
675
|
-
const mcpNames = Object.keys(result.mcp)
|
|
676
|
-
if (mcpNames.length > 0) {
|
|
677
|
-
lines.push('')
|
|
678
|
-
lines.push(' mcp: {')
|
|
679
|
-
for (const name of mcpNames) {
|
|
680
|
-
const server = result.mcp[name]
|
|
681
|
-
lines.push(` ${quote(name)}: {`)
|
|
682
|
-
if (server.url) lines.push(` url: ${quote(server.url)},`)
|
|
683
|
-
if (server.transport && server.transport !== 'http') {
|
|
684
|
-
lines.push(` transport: ${quote(server.transport)},`)
|
|
685
|
-
}
|
|
686
|
-
if (server.command) lines.push(` command: ${quote(server.command)},`)
|
|
687
|
-
if (server.args && server.args.length > 0) {
|
|
688
|
-
lines.push(` args: [${server.args.map(a => quote(a)).join(', ')}],`)
|
|
689
|
-
}
|
|
690
|
-
if (server.env) {
|
|
691
|
-
lines.push(' env: {')
|
|
692
|
-
for (const [k, v] of Object.entries(server.env)) {
|
|
693
|
-
lines.push(` ${quote(k)}: ${quote(v)},`)
|
|
694
|
-
}
|
|
695
|
-
lines.push(' },')
|
|
696
|
-
}
|
|
697
|
-
if (server.auth) {
|
|
698
|
-
lines.push(' auth: {')
|
|
699
|
-
lines.push(` type: ${quote(server.auth.type)},`)
|
|
700
|
-
if (server.auth.envVar) lines.push(` envVar: ${quote(server.auth.envVar)},`)
|
|
701
|
-
if (server.auth.headerName && server.auth.headerName !== 'Authorization') {
|
|
702
|
-
lines.push(` headerName: ${quote(server.auth.headerName)},`)
|
|
703
|
-
}
|
|
704
|
-
if (server.auth.headerTemplate && server.auth.headerTemplate !== 'Bearer ${value}') {
|
|
705
|
-
lines.push(` headerTemplate: ${quote(server.auth.headerTemplate)},`)
|
|
706
|
-
}
|
|
707
|
-
lines.push(' },')
|
|
708
|
-
}
|
|
709
|
-
lines.push(' },')
|
|
710
|
-
}
|
|
711
|
-
lines.push(' },')
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// Hooks
|
|
715
|
-
const hookEvents = Object.keys(result.hooks)
|
|
716
|
-
if (hookEvents.length > 0) {
|
|
717
|
-
lines.push('')
|
|
718
|
-
lines.push(' hooks: {')
|
|
719
|
-
for (const event of hookEvents) {
|
|
720
|
-
const entries = result.hooks[event]
|
|
721
|
-
lines.push(` ${event}: [`)
|
|
722
|
-
for (const entry of entries) {
|
|
723
|
-
const parts: string[] = [`command: ${quote(entry.command)}`]
|
|
724
|
-
if (entry.timeout) parts.push(`timeout: ${entry.timeout}`)
|
|
725
|
-
if (entry.matcher) parts.push(`matcher: ${quote(entry.matcher)}`)
|
|
726
|
-
lines.push(` { ${parts.join(', ')} },`)
|
|
727
|
-
}
|
|
728
|
-
lines.push(' ],')
|
|
729
|
-
}
|
|
730
|
-
lines.push(' },')
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
lines.push('')
|
|
734
|
-
lines.push(` // Migrated from ${result.platform} plugin`)
|
|
735
|
-
lines.push(` targets: ['claude-code', 'cursor', 'codex', 'opencode'],`)
|
|
736
|
-
lines.push('})')
|
|
737
|
-
lines.push('')
|
|
738
|
-
|
|
739
|
-
return lines.join('\n')
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
function quote(s: string): string {
|
|
743
|
-
// Use single quotes, escape single quotes inside
|
|
744
|
-
return `'${s.replace(/'/g, "\\'")}'`
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// ── Main Migrate Function ───────────────────────────────────────
|
|
748
|
-
|
|
749
|
-
export async function migrate(inputPath: string): Promise<void> {
|
|
750
|
-
const pluginDir = resolve(inputPath)
|
|
751
|
-
const outputDir = process.cwd()
|
|
752
|
-
|
|
753
|
-
if (!existsSync(pluginDir)) {
|
|
754
|
-
console.error(`Error: Path does not exist: ${pluginDir}`)
|
|
755
|
-
process.exit(1)
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
console.log(`Scanning ${pluginDir} ...`)
|
|
759
|
-
|
|
760
|
-
// 1. Detect platform
|
|
761
|
-
const detection = detectPlatform(pluginDir)
|
|
762
|
-
if (!detection) {
|
|
763
|
-
console.error('Error: Could not detect plugin platform.')
|
|
764
|
-
console.error('Expected one of:')
|
|
765
|
-
console.error(' .claude-plugin/plugin.json')
|
|
766
|
-
console.error(' .cursor-plugin/plugin.json')
|
|
767
|
-
console.error(' .codex-plugin/plugin.json')
|
|
768
|
-
console.error(' package.json with @opencode-ai/plugin dependency')
|
|
769
|
-
process.exit(1)
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
console.log(`Detected: ${detection.platform} plugin`)
|
|
773
|
-
|
|
774
|
-
// 2. Parse manifest
|
|
775
|
-
const manifest = parseManifest(detection)
|
|
776
|
-
console.log(` name: ${manifest.name ?? '(none)'}`)
|
|
777
|
-
console.log(` version: ${manifest.version ?? '(none)'}`)
|
|
778
|
-
|
|
779
|
-
// 3. Parse MCP
|
|
780
|
-
const mcp = parseMcp(pluginDir, detection)
|
|
781
|
-
const mcpCount = Object.keys(mcp).length
|
|
782
|
-
if (mcpCount > 0) {
|
|
783
|
-
console.log(` mcp servers: ${Object.keys(mcp).join(', ')}`)
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
// 4. Parse hooks
|
|
787
|
-
const hooks = parseHooks(pluginDir, detection)
|
|
788
|
-
const hookCount = Object.keys(hooks).length
|
|
789
|
-
if (hookCount > 0) {
|
|
790
|
-
console.log(` hooks: ${Object.keys(hooks).join(', ')}`)
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
// 5. Find instructions
|
|
794
|
-
const instructions = findInstructions(pluginDir)
|
|
795
|
-
if (instructions) {
|
|
796
|
-
console.log(` instructions: ${instructions}`)
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// 6. Detect directories
|
|
800
|
-
const directories = detectDirectories(pluginDir)
|
|
801
|
-
const persistedSkills = readMigratedSkills(pluginDir, directories)
|
|
802
|
-
const dirNames = Object.entries(directories)
|
|
803
|
-
.filter(([_, exists]) => exists)
|
|
804
|
-
.map(([name]) => name)
|
|
805
|
-
if (dirNames.length > 0) {
|
|
806
|
-
console.log(` directories: ${dirNames.join(', ')}`)
|
|
807
|
-
}
|
|
808
|
-
if (persistedSkills.length > 0) {
|
|
809
|
-
console.log(` migrated skills: ${persistedSkills.map((skill) => skill.dirName).join(', ')}`)
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
// 7. Build result
|
|
813
|
-
const result: MigrateResult = {
|
|
814
|
-
platform: detection.platform,
|
|
815
|
-
manifest,
|
|
816
|
-
mcp,
|
|
817
|
-
hooks,
|
|
818
|
-
instructions,
|
|
819
|
-
directories,
|
|
820
|
-
persistedSkills,
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// 8. Generate config
|
|
824
|
-
const configContent = generateConfigTs(result)
|
|
825
|
-
const configPath = resolve(outputDir, 'pluxx.config.ts')
|
|
826
|
-
|
|
827
|
-
if (existsSync(configPath)) {
|
|
828
|
-
console.error(`\nError: pluxx.config.ts already exists in ${outputDir}`)
|
|
829
|
-
console.error('Remove it first or run from a different directory.')
|
|
830
|
-
process.exit(1)
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
await Bun.write(configPath, configContent)
|
|
834
|
-
console.log(`\nGenerated pluxx.config.ts`)
|
|
835
|
-
|
|
836
|
-
// 9. Copy directories
|
|
837
|
-
const copied = copyDirectories(pluginDir, outputDir, directories)
|
|
838
|
-
if (copied.length > 0) {
|
|
839
|
-
console.log(`Copied: ${copied.map(d => `./${d}/`).join(', ')}`)
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// 10. Copy instructions file if it exists and is not README.md
|
|
843
|
-
if (instructions && instructions !== './README.md') {
|
|
844
|
-
const srcInstr = resolve(pluginDir, instructions)
|
|
845
|
-
const destInstr = resolve(outputDir, instructions)
|
|
846
|
-
if (!existsSync(destInstr)) {
|
|
847
|
-
const content = readFileSync(srcInstr, 'utf-8')
|
|
848
|
-
await Bun.write(destInstr, content)
|
|
849
|
-
console.log(`Copied: ${instructions}`)
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
// 11. Create synthetic migration metadata/taxonomy so Agent Mode and evals work.
|
|
854
|
-
const taxonomyPath = resolve(outputDir, MCP_TAXONOMY_PATH)
|
|
855
|
-
const metadataPath = resolve(outputDir, MCP_SCAFFOLD_METADATA_PATH)
|
|
856
|
-
mkdirSync(resolve(outputDir, '.pluxx'), { recursive: true })
|
|
857
|
-
await Bun.write(taxonomyPath, `${JSON.stringify(result.persistedSkills, null, 2)}\n`)
|
|
858
|
-
await Bun.write(metadataPath, `${JSON.stringify(buildMigratedScaffoldMetadata(result, outputDir), null, 2)}\n`)
|
|
859
|
-
console.log(`Generated: ${MCP_TAXONOMY_PATH}, ${MCP_SCAFFOLD_METADATA_PATH}`)
|
|
860
|
-
|
|
861
|
-
console.log('')
|
|
862
|
-
console.log('Migration complete! Next steps:')
|
|
863
|
-
console.log(' 1. Review pluxx.config.ts and fill in any TODOs')
|
|
864
|
-
console.log(' 2. Run: pluxx doctor')
|
|
865
|
-
console.log(' 3. Run: pluxx eval')
|
|
866
|
-
console.log(' 4. Run: pluxx build')
|
|
867
|
-
}
|