@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.
Files changed (110) hide show
  1. package/README.md +110 -515
  2. package/bin/pluxx.js +19 -28
  3. package/dist/agents.d.ts +16 -0
  4. package/dist/agents.d.ts.map +1 -0
  5. package/dist/cli/agent.d.ts +69 -0
  6. package/dist/cli/agent.d.ts.map +1 -1
  7. package/dist/cli/doctor.d.ts +3 -0
  8. package/dist/cli/doctor.d.ts.map +1 -1
  9. package/dist/cli/entry.d.ts +2 -0
  10. package/dist/cli/entry.d.ts.map +1 -0
  11. package/dist/cli/eval.d.ts +22 -0
  12. package/dist/cli/eval.d.ts.map +1 -0
  13. package/dist/cli/index.d.ts +26 -3
  14. package/dist/cli/index.d.ts.map +1 -1
  15. package/dist/cli/index.js +21810 -0
  16. package/dist/cli/init-from-mcp.d.ts +34 -3
  17. package/dist/cli/init-from-mcp.d.ts.map +1 -1
  18. package/dist/cli/install.d.ts +3 -0
  19. package/dist/cli/install.d.ts.map +1 -1
  20. package/dist/cli/lint.d.ts +7 -1
  21. package/dist/cli/lint.d.ts.map +1 -1
  22. package/dist/cli/mcp-proxy.d.ts +10 -0
  23. package/dist/cli/mcp-proxy.d.ts.map +1 -0
  24. package/dist/cli/migrate.d.ts.map +1 -1
  25. package/dist/cli/primitive-summary.d.ts +14 -0
  26. package/dist/cli/primitive-summary.d.ts.map +1 -0
  27. package/dist/cli/prompt.d.ts +1 -1
  28. package/dist/cli/publish.d.ts +6 -1
  29. package/dist/cli/publish.d.ts.map +1 -1
  30. package/dist/cli/sync-from-mcp.d.ts.map +1 -1
  31. package/dist/cli/test.d.ts +2 -0
  32. package/dist/cli/test.d.ts.map +1 -1
  33. package/dist/cli/verify-install.d.ts +25 -0
  34. package/dist/cli/verify-install.d.ts.map +1 -0
  35. package/dist/commands.d.ts +10 -0
  36. package/dist/commands.d.ts.map +1 -0
  37. package/dist/compiler-intent.d.ts +165 -0
  38. package/dist/compiler-intent.d.ts.map +1 -0
  39. package/dist/config/load.d.ts.map +1 -1
  40. package/dist/delegation.d.ts +11 -0
  41. package/dist/delegation.d.ts.map +1 -0
  42. package/dist/generators/amp/index.d.ts.map +1 -1
  43. package/dist/generators/base.d.ts +5 -0
  44. package/dist/generators/base.d.ts.map +1 -1
  45. package/dist/generators/claude-code/index.d.ts +2 -0
  46. package/dist/generators/claude-code/index.d.ts.map +1 -1
  47. package/dist/generators/cline/index.d.ts.map +1 -1
  48. package/dist/generators/codex/index.d.ts +5 -0
  49. package/dist/generators/codex/index.d.ts.map +1 -1
  50. package/dist/generators/cursor/index.d.ts +1 -0
  51. package/dist/generators/cursor/index.d.ts.map +1 -1
  52. package/dist/generators/gemini-cli/index.d.ts.map +1 -1
  53. package/dist/generators/github-copilot/index.d.ts.map +1 -1
  54. package/dist/generators/opencode/index.d.ts +1 -0
  55. package/dist/generators/opencode/index.d.ts.map +1 -1
  56. package/dist/generators/openhands/index.d.ts.map +1 -1
  57. package/dist/generators/roo-code/index.d.ts.map +1 -1
  58. package/dist/generators/shared/claude-family.d.ts.map +1 -1
  59. package/dist/generators/warp/index.d.ts.map +1 -1
  60. package/dist/index.d.ts +4 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +5464 -548
  63. package/dist/mcp/introspect.d.ts +43 -1
  64. package/dist/mcp/introspect.d.ts.map +1 -1
  65. package/dist/permissions.d.ts.map +1 -1
  66. package/dist/schema.d.ts +91 -42
  67. package/dist/schema.d.ts.map +1 -1
  68. package/dist/text-files.d.ts +5 -0
  69. package/dist/text-files.d.ts.map +1 -0
  70. package/dist/validation/platform-rules.d.ts +35 -1
  71. package/dist/validation/platform-rules.d.ts.map +1 -1
  72. package/package.json +16 -14
  73. package/src/cli/agent.ts +0 -1030
  74. package/src/cli/dev.ts +0 -112
  75. package/src/cli/doctor.ts +0 -588
  76. package/src/cli/index.ts +0 -2414
  77. package/src/cli/init-from-mcp.ts +0 -1611
  78. package/src/cli/install.ts +0 -698
  79. package/src/cli/lint.ts +0 -1219
  80. package/src/cli/migrate.ts +0 -614
  81. package/src/cli/prompt.ts +0 -82
  82. package/src/cli/publish.ts +0 -401
  83. package/src/cli/runtime.ts +0 -86
  84. package/src/cli/sync-from-mcp.ts +0 -563
  85. package/src/cli/test.ts +0 -134
  86. package/src/compatibility/matrix.ts +0 -149
  87. package/src/config/define.ts +0 -20
  88. package/src/config/load.ts +0 -74
  89. package/src/generators/amp/index.ts +0 -63
  90. package/src/generators/base.ts +0 -188
  91. package/src/generators/claude-code/index.ts +0 -29
  92. package/src/generators/cline/index.ts +0 -35
  93. package/src/generators/codex/index.ts +0 -120
  94. package/src/generators/cursor/index.ts +0 -158
  95. package/src/generators/gemini-cli/index.ts +0 -83
  96. package/src/generators/github-copilot/index.ts +0 -32
  97. package/src/generators/hooks-warning.ts +0 -51
  98. package/src/generators/index.ts +0 -71
  99. package/src/generators/opencode/index.ts +0 -526
  100. package/src/generators/openhands/index.ts +0 -32
  101. package/src/generators/roo-code/index.ts +0 -35
  102. package/src/generators/shared/claude-family.ts +0 -215
  103. package/src/generators/warp/index.ts +0 -32
  104. package/src/hook-events.ts +0 -33
  105. package/src/index.ts +0 -23
  106. package/src/mcp/introspect.ts +0 -834
  107. package/src/permissions.ts +0 -258
  108. package/src/schema.ts +0 -312
  109. package/src/user-config.ts +0 -177
  110. package/src/validation/platform-rules.ts +0 -565
@@ -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
- }