@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/install.ts
DELETED
|
@@ -1,860 +0,0 @@
|
|
|
1
|
-
import { resolve, basename, dirname } from 'path'
|
|
2
|
-
import { existsSync, symlinkSync, mkdirSync, rmSync, readFileSync, writeFileSync, cpSync } from 'fs'
|
|
3
|
-
import { spawnSync } from 'child_process'
|
|
4
|
-
import * as readline from 'readline'
|
|
5
|
-
import type { PluginConfig, TargetPlatform, UserConfigEntry } from '../schema'
|
|
6
|
-
import {
|
|
7
|
-
buildUserConfigEnvMap,
|
|
8
|
-
buildUserConfigValueMap,
|
|
9
|
-
collectUserConfigEntries,
|
|
10
|
-
defaultUserConfigEnvVar,
|
|
11
|
-
resolveUserConfigEntriesForTarget,
|
|
12
|
-
type ResolvedUserConfigEntry,
|
|
13
|
-
} from '../user-config'
|
|
14
|
-
|
|
15
|
-
interface InstallTarget {
|
|
16
|
-
platform: TargetPlatform
|
|
17
|
-
pluginDir: string
|
|
18
|
-
description: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface PlannedInstallTarget extends InstallTarget {
|
|
22
|
-
sourceDir: string
|
|
23
|
-
built: boolean
|
|
24
|
-
existing: boolean
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface CommandResult {
|
|
28
|
-
status: number | null
|
|
29
|
-
stdout: string
|
|
30
|
-
stderr: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface CodexMarketplaceFile {
|
|
34
|
-
name?: string
|
|
35
|
-
interface?: {
|
|
36
|
-
displayName?: string
|
|
37
|
-
}
|
|
38
|
-
plugins?: Array<{
|
|
39
|
-
name: string
|
|
40
|
-
source?: {
|
|
41
|
-
source?: string
|
|
42
|
-
path?: string
|
|
43
|
-
}
|
|
44
|
-
policy?: {
|
|
45
|
-
installation?: string
|
|
46
|
-
authentication?: string
|
|
47
|
-
}
|
|
48
|
-
category?: string
|
|
49
|
-
}>
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
type CommandRunner = (command: string, args: string[]) => CommandResult
|
|
53
|
-
|
|
54
|
-
export interface HookCommand {
|
|
55
|
-
event: string
|
|
56
|
-
command: string
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
type PluginHooks = PluginConfig['hooks']
|
|
60
|
-
type UserConfigPrimitive = string | number | boolean
|
|
61
|
-
|
|
62
|
-
interface PlannedUserConfigEntry {
|
|
63
|
-
field: UserConfigEntry
|
|
64
|
-
envVar?: string
|
|
65
|
-
source: 'env' | 'default' | 'missing'
|
|
66
|
-
value?: UserConfigPrimitive
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function listHookCommands(hooks?: PluginHooks): HookCommand[] {
|
|
70
|
-
if (!hooks) return []
|
|
71
|
-
|
|
72
|
-
const commands: HookCommand[] = []
|
|
73
|
-
for (const [event, entries] of Object.entries(hooks)) {
|
|
74
|
-
for (const entry of entries) {
|
|
75
|
-
if (entry.type === 'command' && entry.command) {
|
|
76
|
-
commands.push({ event, command: entry.command })
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return commands
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function planInstallUserConfig(
|
|
85
|
-
config: PluginConfig,
|
|
86
|
-
platforms: TargetPlatform[] = config.targets,
|
|
87
|
-
): PlannedUserConfigEntry[] {
|
|
88
|
-
const entries = collectUserConfigEntries(config, platforms)
|
|
89
|
-
|
|
90
|
-
return entries.map((field) => {
|
|
91
|
-
const envVar = field.envVar ?? defaultUserConfigEnvVar(field.key)
|
|
92
|
-
const envValue = process.env[envVar]
|
|
93
|
-
if (envValue !== undefined && envValue !== '') {
|
|
94
|
-
return {
|
|
95
|
-
field,
|
|
96
|
-
envVar,
|
|
97
|
-
source: 'env',
|
|
98
|
-
value: parseUserConfigValue(field, envValue),
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (field.defaultValue !== undefined) {
|
|
103
|
-
return {
|
|
104
|
-
field,
|
|
105
|
-
envVar,
|
|
106
|
-
source: 'default',
|
|
107
|
-
value: field.defaultValue,
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
field,
|
|
113
|
-
envVar,
|
|
114
|
-
source: 'missing',
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export async function resolveInstallUserConfig(
|
|
120
|
-
config: PluginConfig,
|
|
121
|
-
platforms: TargetPlatform[] = config.targets,
|
|
122
|
-
options: { isTTY?: boolean } = {},
|
|
123
|
-
): Promise<ResolvedUserConfigEntry[]> {
|
|
124
|
-
const planned = planInstallUserConfig(config, platforms)
|
|
125
|
-
const resolved: ResolvedUserConfigEntry[] = []
|
|
126
|
-
const isTTY = options.isTTY ?? process.stdin.isTTY === true
|
|
127
|
-
|
|
128
|
-
for (const entry of planned) {
|
|
129
|
-
if (entry.value !== undefined) {
|
|
130
|
-
resolved.push({
|
|
131
|
-
field: entry.field,
|
|
132
|
-
value: entry.value,
|
|
133
|
-
envVar: entry.envVar,
|
|
134
|
-
})
|
|
135
|
-
continue
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (entry.field.required === false) {
|
|
139
|
-
continue
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (!isTTY) {
|
|
143
|
-
const hint = entry.envVar ? ` Export ${entry.envVar} or install interactively.` : ' Re-run interactively to provide it.'
|
|
144
|
-
throw new Error(`Missing required userConfig "${entry.field.key}".${hint}`)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const promptLabel = entry.field.title || entry.field.key
|
|
148
|
-
const envHint = entry.envVar ? ` [env: ${entry.envVar}]` : ''
|
|
149
|
-
const answer = await promptTextValue(`${promptLabel}${envHint}: `)
|
|
150
|
-
const value = parseUserConfigValue(entry.field, answer)
|
|
151
|
-
|
|
152
|
-
resolved.push({
|
|
153
|
-
field: entry.field,
|
|
154
|
-
value,
|
|
155
|
-
envVar: entry.envVar,
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return resolved
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async function promptTextValue(question: string): Promise<string> {
|
|
163
|
-
const rl = readline.createInterface({
|
|
164
|
-
input: process.stdin,
|
|
165
|
-
output: process.stdout,
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
try {
|
|
169
|
-
const answer = await new Promise<string>((resolveAnswer) => {
|
|
170
|
-
rl.question(question, (value) => resolveAnswer(value))
|
|
171
|
-
})
|
|
172
|
-
return answer
|
|
173
|
-
} finally {
|
|
174
|
-
rl.close()
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function parseUserConfigValue(field: UserConfigEntry, rawValue: string): UserConfigPrimitive {
|
|
179
|
-
if (field.type === 'number') {
|
|
180
|
-
const parsed = Number(rawValue)
|
|
181
|
-
if (!Number.isFinite(parsed)) {
|
|
182
|
-
throw new Error(`Expected a numeric value for userConfig "${field.key}".`)
|
|
183
|
-
}
|
|
184
|
-
return parsed
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (field.type === 'boolean') {
|
|
188
|
-
const normalized = rawValue.trim().toLowerCase()
|
|
189
|
-
if (['true', '1', 'yes', 'y'].includes(normalized)) return true
|
|
190
|
-
if (['false', '0', 'no', 'n'].includes(normalized)) return false
|
|
191
|
-
throw new Error(`Expected a boolean value for userConfig "${field.key}".`)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return rawValue
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
async function promptTrustConfirmation(question: string): Promise<boolean> {
|
|
198
|
-
const rl = readline.createInterface({
|
|
199
|
-
input: process.stdin,
|
|
200
|
-
output: process.stdout,
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
const answer = await new Promise<string>((resolveAnswer) => {
|
|
205
|
-
rl.question(question, (value) => resolveAnswer(value))
|
|
206
|
-
})
|
|
207
|
-
const normalized = answer.trim().toLowerCase()
|
|
208
|
-
return normalized === 'y' || normalized === 'yes'
|
|
209
|
-
} finally {
|
|
210
|
-
rl.close()
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
interface EnsureHookTrustOptions {
|
|
215
|
-
pluginName: string
|
|
216
|
-
hooks?: PluginHooks
|
|
217
|
-
trust?: boolean
|
|
218
|
-
isTTY?: boolean
|
|
219
|
-
confirmPrompt?: (question: string) => Promise<boolean>
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export async function ensureHookTrust(options: EnsureHookTrustOptions): Promise<void> {
|
|
223
|
-
const commands = listHookCommands(options.hooks)
|
|
224
|
-
if (commands.length === 0) return
|
|
225
|
-
if (options.trust) return
|
|
226
|
-
|
|
227
|
-
console.warn('\n⚠️ This plugin defines hook commands that run shell code on your machine:')
|
|
228
|
-
console.warn('')
|
|
229
|
-
for (const { event, command } of commands) {
|
|
230
|
-
console.warn(` - ${event}: ${command}`)
|
|
231
|
-
}
|
|
232
|
-
console.warn('')
|
|
233
|
-
console.warn(
|
|
234
|
-
`Installing "${options.pluginName}" means trusting this plugin author with local command execution.`
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
const isTTY = options.isTTY ?? process.stdin.isTTY === true
|
|
238
|
-
if (!isTTY) {
|
|
239
|
-
throw new Error(
|
|
240
|
-
`Refusing to install plugin with hooks in non-interactive mode. Re-run with --trust to continue.`
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const confirm = options.confirmPrompt ?? promptTrustConfirmation
|
|
245
|
-
const approved = await confirm('Continue install? (y/N): ')
|
|
246
|
-
if (!approved) {
|
|
247
|
-
throw new Error('Install cancelled. Re-run with --trust to bypass confirmation.')
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function getInstallTargets(pluginName: string): InstallTarget[] {
|
|
252
|
-
const home = process.env.HOME ?? '~'
|
|
253
|
-
return [
|
|
254
|
-
{
|
|
255
|
-
platform: 'claude-code',
|
|
256
|
-
pluginDir: resolve(home, '.claude/plugins', pluginName),
|
|
257
|
-
description: `claude plugin install ${pluginName}@${getClaudeMarketplaceName(pluginName)}`,
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
platform: 'cursor',
|
|
261
|
-
pluginDir: resolve(home, '.cursor/plugins/local', pluginName),
|
|
262
|
-
description: `~/.cursor/plugins/local/${pluginName}`,
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
platform: 'codex',
|
|
266
|
-
pluginDir: resolve(home, '.codex/plugins', pluginName),
|
|
267
|
-
description: `~/.codex/plugins/${pluginName} (via ~/.agents/plugins/marketplace.json)`,
|
|
268
|
-
},
|
|
269
|
-
{
|
|
270
|
-
platform: 'opencode',
|
|
271
|
-
pluginDir: resolve(home, '.config/opencode/plugins', pluginName),
|
|
272
|
-
description: `~/.config/opencode/plugins/${pluginName}`,
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
platform: 'github-copilot',
|
|
276
|
-
pluginDir: resolve(home, '.github-copilot/plugins', pluginName),
|
|
277
|
-
description: `~/.github-copilot/plugins/${pluginName}`,
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
platform: 'openhands',
|
|
281
|
-
pluginDir: resolve(home, '.openhands/plugins', pluginName),
|
|
282
|
-
description: `~/.openhands/plugins/${pluginName}`,
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
platform: 'warp',
|
|
286
|
-
pluginDir: resolve(home, '.warp/plugins', pluginName),
|
|
287
|
-
description: `~/.warp/plugins/${pluginName}`,
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
platform: 'gemini-cli',
|
|
291
|
-
pluginDir: resolve(home, '.gemini/extensions', pluginName),
|
|
292
|
-
description: `~/.gemini/extensions/${pluginName}`,
|
|
293
|
-
},
|
|
294
|
-
{
|
|
295
|
-
platform: 'roo-code',
|
|
296
|
-
pluginDir: resolve(home, '.roo/plugins', pluginName),
|
|
297
|
-
description: `~/.roo/plugins/${pluginName}`,
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
platform: 'cline',
|
|
301
|
-
pluginDir: resolve(home, '.cline/plugins', pluginName),
|
|
302
|
-
description: `~/.cline/plugins/${pluginName}`,
|
|
303
|
-
},
|
|
304
|
-
{
|
|
305
|
-
platform: 'amp',
|
|
306
|
-
pluginDir: resolve(home, '.amp/plugins', pluginName),
|
|
307
|
-
description: `~/.amp/plugins/${pluginName}`,
|
|
308
|
-
},
|
|
309
|
-
]
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
export function getInstallFollowupNotes(platforms: TargetPlatform[]): string[] {
|
|
313
|
-
const notes: string[] = []
|
|
314
|
-
|
|
315
|
-
if (platforms.includes('claude-code')) {
|
|
316
|
-
notes.push('Claude Code note: if Claude is already open, run /reload-plugins in the session to pick up the new install.')
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return notes
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function runCommandDefault(command: string, args: string[]): CommandResult {
|
|
323
|
-
const result = spawnSync(command, args, { encoding: 'utf-8' })
|
|
324
|
-
return {
|
|
325
|
-
status: result.status,
|
|
326
|
-
stdout: result.stdout ?? '',
|
|
327
|
-
stderr: result.stderr ?? '',
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function createSymlinkInstall(target: PlannedInstallTarget): void {
|
|
332
|
-
const parentDir = resolve(target.pluginDir, '..')
|
|
333
|
-
mkdirSync(parentDir, { recursive: true })
|
|
334
|
-
|
|
335
|
-
if (existsSync(target.pluginDir)) {
|
|
336
|
-
rmSync(target.pluginDir, { recursive: true, force: true })
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
symlinkSync(target.sourceDir, target.pluginDir)
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function getCodexMarketplacePath(): string {
|
|
343
|
-
const home = process.env.HOME ?? '~'
|
|
344
|
-
return resolve(home, '.agents/plugins/marketplace.json')
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function getCodexMarketplacePluginPath(pluginName: string): string {
|
|
348
|
-
return `./.codex/plugins/${pluginName}`
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function readCodexMarketplace(filepath: string): CodexMarketplaceFile {
|
|
352
|
-
if (!existsSync(filepath)) {
|
|
353
|
-
return {
|
|
354
|
-
name: 'pluxx-local',
|
|
355
|
-
interface: {
|
|
356
|
-
displayName: 'Pluxx Local',
|
|
357
|
-
},
|
|
358
|
-
plugins: [],
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const raw = readFileSync(filepath, 'utf-8')
|
|
363
|
-
const parsed = JSON.parse(raw) as CodexMarketplaceFile
|
|
364
|
-
return {
|
|
365
|
-
name: parsed.name ?? 'pluxx-local',
|
|
366
|
-
interface: parsed.interface ?? { displayName: 'Pluxx Local' },
|
|
367
|
-
plugins: Array.isArray(parsed.plugins) ? parsed.plugins : [],
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function ensureCodexMarketplace(pluginName: string): void {
|
|
372
|
-
const filepath = getCodexMarketplacePath()
|
|
373
|
-
mkdirSync(dirname(filepath), { recursive: true })
|
|
374
|
-
|
|
375
|
-
const marketplace = readCodexMarketplace(filepath)
|
|
376
|
-
const nextPlugins = (marketplace.plugins ?? []).filter((plugin) => plugin.name !== pluginName)
|
|
377
|
-
nextPlugins.push({
|
|
378
|
-
name: pluginName,
|
|
379
|
-
source: {
|
|
380
|
-
source: 'local',
|
|
381
|
-
path: getCodexMarketplacePluginPath(pluginName),
|
|
382
|
-
},
|
|
383
|
-
policy: {
|
|
384
|
-
installation: 'AVAILABLE',
|
|
385
|
-
authentication: 'ON_INSTALL',
|
|
386
|
-
},
|
|
387
|
-
category: 'Productivity',
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
writeFileSync(
|
|
391
|
-
filepath,
|
|
392
|
-
JSON.stringify({
|
|
393
|
-
name: marketplace.name ?? 'pluxx-local',
|
|
394
|
-
interface: marketplace.interface ?? { displayName: 'Pluxx Local' },
|
|
395
|
-
plugins: nextPlugins,
|
|
396
|
-
}, null, 2) + '\n',
|
|
397
|
-
)
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function removeCodexMarketplacePlugin(pluginName: string): void {
|
|
401
|
-
const filepath = getCodexMarketplacePath()
|
|
402
|
-
if (!existsSync(filepath)) return
|
|
403
|
-
|
|
404
|
-
const marketplace = readCodexMarketplace(filepath)
|
|
405
|
-
const nextPlugins = (marketplace.plugins ?? []).filter((plugin) => plugin.name !== pluginName)
|
|
406
|
-
|
|
407
|
-
if (nextPlugins.length === (marketplace.plugins ?? []).length) {
|
|
408
|
-
return
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (nextPlugins.length === 0) {
|
|
412
|
-
rmSync(filepath, { force: true })
|
|
413
|
-
return
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
writeFileSync(
|
|
417
|
-
filepath,
|
|
418
|
-
JSON.stringify({
|
|
419
|
-
name: marketplace.name ?? 'pluxx-local',
|
|
420
|
-
interface: marketplace.interface ?? { displayName: 'Pluxx Local' },
|
|
421
|
-
plugins: nextPlugins,
|
|
422
|
-
}, null, 2) + '\n',
|
|
423
|
-
)
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function createCopiedInstall(target: PlannedInstallTarget): void {
|
|
427
|
-
const parentDir = resolve(target.pluginDir, '..')
|
|
428
|
-
mkdirSync(parentDir, { recursive: true })
|
|
429
|
-
|
|
430
|
-
if (existsSync(target.pluginDir)) {
|
|
431
|
-
rmSync(target.pluginDir, { recursive: true, force: true })
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
cpSync(target.sourceDir, target.pluginDir, { recursive: true })
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
function materializeTemplateValue(value: string, env: Record<string, string>): string {
|
|
438
|
-
return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_match, name: string) => env[name] ?? `\${${name}}`)
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function materializeEnvRecord(
|
|
442
|
-
input: Record<string, string> | undefined,
|
|
443
|
-
env: Record<string, string>,
|
|
444
|
-
): Record<string, string> {
|
|
445
|
-
const output: Record<string, string> = {}
|
|
446
|
-
|
|
447
|
-
for (const [key, value] of Object.entries(input ?? {})) {
|
|
448
|
-
output[key] = materializeTemplateValue(value, env)
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return output
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function patchInstalledMcpConfig(
|
|
455
|
-
pluginDir: string,
|
|
456
|
-
platform: TargetPlatform,
|
|
457
|
-
config: PluginConfig,
|
|
458
|
-
entries: ResolvedUserConfigEntry[],
|
|
459
|
-
): void {
|
|
460
|
-
if (!config.mcp) return
|
|
461
|
-
|
|
462
|
-
const env = buildUserConfigEnvMap(entries)
|
|
463
|
-
|
|
464
|
-
if (platform === 'claude-code' || platform === 'cursor') {
|
|
465
|
-
const filepath = resolve(pluginDir, platform === 'claude-code' ? '.mcp.json' : 'mcp.json')
|
|
466
|
-
if (!existsSync(filepath)) return
|
|
467
|
-
|
|
468
|
-
const mcpServers: Record<string, unknown> = {}
|
|
469
|
-
const usesPlatformManagedAuth = platform === 'claude-code'
|
|
470
|
-
? config.platforms?.['claude-code']?.mcpAuth === 'platform'
|
|
471
|
-
: config.platforms?.cursor?.mcpAuth === 'platform'
|
|
472
|
-
|
|
473
|
-
for (const [name, server] of Object.entries(config.mcp)) {
|
|
474
|
-
if (server.transport === 'stdio') {
|
|
475
|
-
mcpServers[name] = {
|
|
476
|
-
command: server.command,
|
|
477
|
-
args: server.args ?? [],
|
|
478
|
-
env: materializeEnvRecord(server.env, env),
|
|
479
|
-
}
|
|
480
|
-
continue
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
const entry: Record<string, unknown> = {
|
|
484
|
-
type: server.transport === 'sse' ? 'sse' : 'http',
|
|
485
|
-
url: server.url,
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (!usesPlatformManagedAuth && server.auth?.type === 'bearer' && server.auth.envVar && env[server.auth.envVar]) {
|
|
489
|
-
entry.headers = {
|
|
490
|
-
Authorization: `Bearer ${env[server.auth.envVar]}`,
|
|
491
|
-
}
|
|
492
|
-
} else if (!usesPlatformManagedAuth && server.auth?.type === 'header' && server.auth.envVar && env[server.auth.envVar]) {
|
|
493
|
-
entry.headers = {
|
|
494
|
-
[server.auth.headerName]: server.auth.headerTemplate.replace('${value}', env[server.auth.envVar]),
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
mcpServers[name] = entry
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
writeFileSync(filepath, JSON.stringify({ mcpServers }, null, 2) + '\n')
|
|
502
|
-
return
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
if (platform === 'codex') {
|
|
506
|
-
const filepath = resolve(pluginDir, '.mcp.json')
|
|
507
|
-
if (!existsSync(filepath)) return
|
|
508
|
-
|
|
509
|
-
const mcpServers: Record<string, unknown> = {}
|
|
510
|
-
|
|
511
|
-
for (const [name, server] of Object.entries(config.mcp)) {
|
|
512
|
-
if (server.transport === 'stdio') {
|
|
513
|
-
mcpServers[name] = {
|
|
514
|
-
command: server.command,
|
|
515
|
-
args: server.args ?? [],
|
|
516
|
-
env: materializeEnvRecord(server.env, env),
|
|
517
|
-
}
|
|
518
|
-
continue
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
const entry: Record<string, unknown> = {
|
|
522
|
-
url: server.url,
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
if (server.auth?.type === 'bearer' && server.auth.envVar && env[server.auth.envVar]) {
|
|
526
|
-
entry.http_headers = {
|
|
527
|
-
Authorization: `Bearer ${env[server.auth.envVar]}`,
|
|
528
|
-
}
|
|
529
|
-
} else if (server.auth?.type === 'header' && server.auth.envVar && env[server.auth.envVar]) {
|
|
530
|
-
entry.http_headers = {
|
|
531
|
-
[server.auth.headerName]: server.auth.headerTemplate.replace('${value}', env[server.auth.envVar]),
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
mcpServers[name] = entry
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
writeFileSync(filepath, JSON.stringify({ mcpServers }, null, 2) + '\n')
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function writeInstalledUserConfig(
|
|
543
|
-
pluginDir: string,
|
|
544
|
-
entries: ResolvedUserConfigEntry[],
|
|
545
|
-
): void {
|
|
546
|
-
if (entries.length === 0) return
|
|
547
|
-
|
|
548
|
-
const filepath = resolve(pluginDir, '.pluxx-user.json')
|
|
549
|
-
const payload = {
|
|
550
|
-
values: buildUserConfigValueMap(entries),
|
|
551
|
-
env: buildUserConfigEnvMap(entries),
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
writeFileSync(filepath, JSON.stringify(payload, null, 2) + '\n')
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
function disableInstalledEnvValidation(pluginDir: string, entries: ResolvedUserConfigEntry[]): void {
|
|
558
|
-
if (entries.length === 0) return
|
|
559
|
-
|
|
560
|
-
const filepath = resolve(pluginDir, 'scripts/check-env.sh')
|
|
561
|
-
if (!existsSync(filepath)) return
|
|
562
|
-
|
|
563
|
-
writeFileSync(
|
|
564
|
-
filepath,
|
|
565
|
-
'#!/usr/bin/env bash\nset -euo pipefail\n# pluxx install materialized required config for this local plugin install.\nexit 0\n',
|
|
566
|
-
)
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function materializeInstalledPlugin(
|
|
570
|
-
pluginDir: string,
|
|
571
|
-
platform: TargetPlatform,
|
|
572
|
-
config: PluginConfig,
|
|
573
|
-
entries: ResolvedUserConfigEntry[],
|
|
574
|
-
): void {
|
|
575
|
-
if (entries.length === 0) return
|
|
576
|
-
|
|
577
|
-
writeInstalledUserConfig(pluginDir, entries)
|
|
578
|
-
disableInstalledEnvValidation(pluginDir, entries)
|
|
579
|
-
patchInstalledMcpConfig(pluginDir, platform, config, entries)
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
function getClaudeMarketplaceName(pluginName: string): string {
|
|
583
|
-
return `pluxx-local-${pluginName}`
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
function getClaudeMarketplaceRoot(pluginName: string): string {
|
|
587
|
-
const home = process.env.HOME ?? '~'
|
|
588
|
-
return resolve(home, '.claude/plugins/data', getClaudeMarketplaceName(pluginName))
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
function ensureClaudeMarketplace(
|
|
592
|
-
pluginName: string,
|
|
593
|
-
sourceDir: string,
|
|
594
|
-
materialized?: {
|
|
595
|
-
config: PluginConfig
|
|
596
|
-
entries: ResolvedUserConfigEntry[]
|
|
597
|
-
},
|
|
598
|
-
): { marketplaceName: string; marketplaceRoot: string } {
|
|
599
|
-
const marketplaceName = getClaudeMarketplaceName(pluginName)
|
|
600
|
-
const marketplaceRoot = getClaudeMarketplaceRoot(pluginName)
|
|
601
|
-
const marketplaceManifestDir = resolve(marketplaceRoot, '.claude-plugin')
|
|
602
|
-
const marketplacePluginDir = resolve(marketplaceRoot, 'plugins', pluginName)
|
|
603
|
-
const pluginManifestPath = resolve(sourceDir, '.claude-plugin/plugin.json')
|
|
604
|
-
|
|
605
|
-
const pluginManifest = JSON.parse(readFileSync(pluginManifestPath, 'utf-8')) as {
|
|
606
|
-
description?: string
|
|
607
|
-
version?: string
|
|
608
|
-
author?: unknown
|
|
609
|
-
license?: string
|
|
610
|
-
homepage?: string
|
|
611
|
-
repository?: string
|
|
612
|
-
keywords?: string[]
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
rmSync(marketplaceRoot, { recursive: true, force: true })
|
|
616
|
-
mkdirSync(marketplaceManifestDir, { recursive: true })
|
|
617
|
-
mkdirSync(resolve(marketplaceRoot, 'plugins'), { recursive: true })
|
|
618
|
-
if (materialized && materialized.entries.length > 0) {
|
|
619
|
-
cpSync(sourceDir, marketplacePluginDir, { recursive: true })
|
|
620
|
-
materializeInstalledPlugin(marketplacePluginDir, 'claude-code', materialized.config, materialized.entries)
|
|
621
|
-
} else {
|
|
622
|
-
symlinkSync(sourceDir, marketplacePluginDir)
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
writeFileSync(
|
|
626
|
-
resolve(marketplaceManifestDir, 'marketplace.json'),
|
|
627
|
-
JSON.stringify({
|
|
628
|
-
name: marketplaceName,
|
|
629
|
-
owner: {
|
|
630
|
-
name: 'Pluxx',
|
|
631
|
-
},
|
|
632
|
-
plugins: [
|
|
633
|
-
{
|
|
634
|
-
name: pluginName,
|
|
635
|
-
source: `./plugins/${pluginName}`,
|
|
636
|
-
description: pluginManifest.description ?? `Local Pluxx-built ${pluginName} plugin.`,
|
|
637
|
-
version: pluginManifest.version ?? '0.1.0',
|
|
638
|
-
author: pluginManifest.author ?? { name: 'Pluxx' },
|
|
639
|
-
license: pluginManifest.license ?? 'MIT',
|
|
640
|
-
...(pluginManifest.homepage ? { homepage: pluginManifest.homepage } : {}),
|
|
641
|
-
...(pluginManifest.repository ? { repository: pluginManifest.repository } : {}),
|
|
642
|
-
...(pluginManifest.keywords ? { keywords: pluginManifest.keywords } : {}),
|
|
643
|
-
},
|
|
644
|
-
],
|
|
645
|
-
}, null, 2),
|
|
646
|
-
)
|
|
647
|
-
|
|
648
|
-
return { marketplaceName, marketplaceRoot }
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
function ensureClaudeMarketplaceRegistered(
|
|
652
|
-
pluginName: string,
|
|
653
|
-
sourceDir: string,
|
|
654
|
-
runCommand: CommandRunner,
|
|
655
|
-
materialized?: {
|
|
656
|
-
config: PluginConfig
|
|
657
|
-
entries: ResolvedUserConfigEntry[]
|
|
658
|
-
},
|
|
659
|
-
): string {
|
|
660
|
-
const { marketplaceName, marketplaceRoot } = ensureClaudeMarketplace(pluginName, sourceDir, materialized)
|
|
661
|
-
const marketplaces = runCommand('claude', ['plugin', 'marketplace', 'list', '--json'])
|
|
662
|
-
|
|
663
|
-
if (marketplaces.status !== 0) {
|
|
664
|
-
throw new Error(`Failed to list Claude marketplaces: ${marketplaces.stderr || marketplaces.stdout}`)
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
const known = JSON.parse(marketplaces.stdout) as Array<{ name?: string }>
|
|
668
|
-
if (!known.some(entry => entry.name === marketplaceName)) {
|
|
669
|
-
const add = runCommand('claude', ['plugin', 'marketplace', 'add', marketplaceRoot])
|
|
670
|
-
if (add.status !== 0) {
|
|
671
|
-
throw new Error(`Failed to add Claude marketplace: ${add.stderr || add.stdout}`)
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
return marketplaceName
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
function installClaudePlugin(
|
|
679
|
-
target: PlannedInstallTarget,
|
|
680
|
-
pluginName: string,
|
|
681
|
-
runCommand: CommandRunner,
|
|
682
|
-
materialized?: {
|
|
683
|
-
config: PluginConfig
|
|
684
|
-
entries: ResolvedUserConfigEntry[]
|
|
685
|
-
},
|
|
686
|
-
): void {
|
|
687
|
-
const marketplaceName = ensureClaudeMarketplaceRegistered(pluginName, target.sourceDir, runCommand, materialized)
|
|
688
|
-
|
|
689
|
-
if (existsSync(target.pluginDir)) {
|
|
690
|
-
rmSync(target.pluginDir, { recursive: true, force: true })
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
runCommand('claude', ['plugin', 'uninstall', `${pluginName}@${marketplaceName}`])
|
|
694
|
-
|
|
695
|
-
const install = runCommand('claude', ['plugin', 'install', `${pluginName}@${marketplaceName}`, '--scope', 'user'])
|
|
696
|
-
if (install.status !== 0) {
|
|
697
|
-
throw new Error(`Failed to install Claude plugin: ${install.stderr || install.stdout}`)
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
function uninstallClaudePlugin(
|
|
702
|
-
target: InstallTarget,
|
|
703
|
-
pluginName: string,
|
|
704
|
-
runCommand: CommandRunner,
|
|
705
|
-
options: { quiet?: boolean } = {},
|
|
706
|
-
): boolean {
|
|
707
|
-
const marketplaceName = getClaudeMarketplaceName(pluginName)
|
|
708
|
-
const uninstall = runCommand('claude', ['plugin', 'uninstall', `${pluginName}@${marketplaceName}`])
|
|
709
|
-
|
|
710
|
-
if (uninstall.status !== 0 && !options.quiet) {
|
|
711
|
-
const detail = uninstall.stderr || uninstall.stdout
|
|
712
|
-
if (detail.trim().length > 0) {
|
|
713
|
-
console.warn(` warning claude-code uninstall: ${detail.trim()}`)
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
const marketplaceRoot = getClaudeMarketplaceRoot(pluginName)
|
|
718
|
-
const hadMarketplaceRoot = existsSync(marketplaceRoot)
|
|
719
|
-
rmSync(marketplaceRoot, { recursive: true, force: true })
|
|
720
|
-
|
|
721
|
-
const hadLegacyPluginDir = existsSync(target.pluginDir)
|
|
722
|
-
if (hadLegacyPluginDir) {
|
|
723
|
-
rmSync(target.pluginDir, { recursive: true, force: true })
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
return uninstall.status === 0 || hadMarketplaceRoot || hadLegacyPluginDir
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
export function planInstallPlugin(
|
|
730
|
-
distDir: string,
|
|
731
|
-
pluginName: string,
|
|
732
|
-
platforms?: TargetPlatform[],
|
|
733
|
-
): PlannedInstallTarget[] {
|
|
734
|
-
const targets = getInstallTargets(pluginName)
|
|
735
|
-
const filtered = platforms
|
|
736
|
-
? targets.filter(t => platforms.includes(t.platform))
|
|
737
|
-
: targets
|
|
738
|
-
|
|
739
|
-
return filtered.map((target) => {
|
|
740
|
-
const sourceDir = resolve(distDir, target.platform)
|
|
741
|
-
return {
|
|
742
|
-
...target,
|
|
743
|
-
sourceDir,
|
|
744
|
-
built: existsSync(sourceDir),
|
|
745
|
-
existing: existsSync(target.pluginDir),
|
|
746
|
-
}
|
|
747
|
-
})
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
export async function installPlugin(
|
|
751
|
-
distDir: string,
|
|
752
|
-
pluginName: string,
|
|
753
|
-
platforms?: TargetPlatform[],
|
|
754
|
-
options: {
|
|
755
|
-
config?: PluginConfig
|
|
756
|
-
quiet?: boolean
|
|
757
|
-
useNativeClaudeInstall?: boolean
|
|
758
|
-
runCommand?: CommandRunner
|
|
759
|
-
resolvedUserConfig?: ResolvedUserConfigEntry[]
|
|
760
|
-
} = {},
|
|
761
|
-
): Promise<void> {
|
|
762
|
-
const filtered = planInstallPlugin(distDir, pluginName, platforms)
|
|
763
|
-
const runCommand = options.runCommand ?? runCommandDefault
|
|
764
|
-
const useNativeClaudeInstall = options.useNativeClaudeInstall ?? true
|
|
765
|
-
|
|
766
|
-
let installed = 0
|
|
767
|
-
|
|
768
|
-
for (const target of filtered) {
|
|
769
|
-
if (!target.built) {
|
|
770
|
-
if (!options.quiet) {
|
|
771
|
-
console.log(` skip ${target.platform} (not built)`)
|
|
772
|
-
}
|
|
773
|
-
continue
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
const targetConfigEntries = options.resolvedUserConfig
|
|
777
|
-
? resolveUserConfigEntriesForTarget(options.resolvedUserConfig, target.platform)
|
|
778
|
-
: []
|
|
779
|
-
const shouldMaterialize = targetConfigEntries.length > 0 && options.config
|
|
780
|
-
|
|
781
|
-
if (target.platform === 'claude-code' && useNativeClaudeInstall) {
|
|
782
|
-
installClaudePlugin(
|
|
783
|
-
target,
|
|
784
|
-
pluginName,
|
|
785
|
-
runCommand,
|
|
786
|
-
shouldMaterialize
|
|
787
|
-
? {
|
|
788
|
-
config: options.config!,
|
|
789
|
-
entries: targetConfigEntries,
|
|
790
|
-
}
|
|
791
|
-
: undefined,
|
|
792
|
-
)
|
|
793
|
-
} else if (shouldMaterialize) {
|
|
794
|
-
createCopiedInstall(target)
|
|
795
|
-
materializeInstalledPlugin(target.pluginDir, target.platform, options.config!, targetConfigEntries)
|
|
796
|
-
} else {
|
|
797
|
-
createSymlinkInstall(target)
|
|
798
|
-
}
|
|
799
|
-
if (target.platform === 'codex') {
|
|
800
|
-
ensureCodexMarketplace(pluginName)
|
|
801
|
-
}
|
|
802
|
-
if (!options.quiet) {
|
|
803
|
-
console.log(` ${target.platform} -> ${target.description}`)
|
|
804
|
-
}
|
|
805
|
-
installed++
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
if (installed === 0 && !options.quiet) {
|
|
809
|
-
console.log('Nothing to install. Run `pluxx build` first.')
|
|
810
|
-
} else if (!options.quiet) {
|
|
811
|
-
console.log(`\nInstalled ${installed} plugin(s). Reload or restart your tools to pick them up.`)
|
|
812
|
-
for (const note of getInstallFollowupNotes(filtered.map((target) => target.platform))) {
|
|
813
|
-
console.log(note)
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
export async function uninstallPlugin(
|
|
819
|
-
pluginName: string,
|
|
820
|
-
platforms?: TargetPlatform[],
|
|
821
|
-
options: { quiet?: boolean; runCommand?: CommandRunner } = {},
|
|
822
|
-
): Promise<void> {
|
|
823
|
-
const targets = getInstallTargets(pluginName)
|
|
824
|
-
const filtered = platforms
|
|
825
|
-
? targets.filter(t => platforms.includes(t.platform))
|
|
826
|
-
: targets
|
|
827
|
-
const runCommand = options.runCommand ?? runCommandDefault
|
|
828
|
-
|
|
829
|
-
let removed = 0
|
|
830
|
-
|
|
831
|
-
for (const target of filtered) {
|
|
832
|
-
if (target.platform === 'claude-code') {
|
|
833
|
-
const removedClaude = uninstallClaudePlugin(target, pluginName, runCommand, { quiet: options.quiet })
|
|
834
|
-
if (removedClaude) {
|
|
835
|
-
if (!options.quiet) {
|
|
836
|
-
console.log(` removed ${target.description}`)
|
|
837
|
-
}
|
|
838
|
-
removed++
|
|
839
|
-
}
|
|
840
|
-
continue
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
if (existsSync(target.pluginDir)) {
|
|
844
|
-
rmSync(target.pluginDir, { recursive: true, force: true })
|
|
845
|
-
if (!options.quiet) {
|
|
846
|
-
console.log(` removed ${target.description}`)
|
|
847
|
-
}
|
|
848
|
-
removed++
|
|
849
|
-
}
|
|
850
|
-
if (target.platform === 'codex') {
|
|
851
|
-
removeCodexMarketplacePlugin(pluginName)
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
if (removed === 0 && !options.quiet) {
|
|
856
|
-
console.log('Nothing to uninstall.')
|
|
857
|
-
} else if (!options.quiet) {
|
|
858
|
-
console.log(`\nRemoved ${removed} plugin(s).`)
|
|
859
|
-
}
|
|
860
|
-
}
|