@orchid-labs/pluxx 0.1.0 → 0.1.1
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 +100 -522
- package/dist/cli/agent.d.ts +7 -0
- package/dist/cli/agent.d.ts.map +1 -1
- package/dist/cli/doctor.d.ts +1 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/eval.d.ts +22 -0
- package/dist/cli/eval.d.ts.map +1 -0
- package/dist/cli/index.d.ts +19 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/init-from-mcp.d.ts +17 -2
- package/dist/cli/init-from-mcp.d.ts.map +1 -1
- package/dist/cli/install.d.ts +2 -0
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/lint.d.ts +5 -1
- package/dist/cli/lint.d.ts.map +1 -1
- package/dist/cli/mcp-proxy.d.ts +10 -0
- package/dist/cli/mcp-proxy.d.ts.map +1 -0
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/sync-from-mcp.d.ts.map +1 -1
- package/dist/cli/test.d.ts +2 -0
- package/dist/cli/test.d.ts.map +1 -1
- package/dist/generators/claude-code/index.d.ts +2 -0
- package/dist/generators/claude-code/index.d.ts.map +1 -1
- package/dist/generators/codex/index.d.ts +1 -0
- package/dist/generators/codex/index.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +99 -1
- package/dist/mcp/introspect.d.ts +43 -1
- package/dist/mcp/introspect.d.ts.map +1 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/validation/platform-rules.d.ts +20 -0
- package/dist/validation/platform-rules.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/cli/agent.ts +459 -34
- package/src/cli/doctor.ts +400 -1
- package/src/cli/eval.ts +470 -0
- package/src/cli/index.ts +633 -114
- package/src/cli/init-from-mcp.ts +545 -41
- package/src/cli/install.ts +166 -4
- package/src/cli/lint.ts +56 -26
- package/src/cli/mcp-proxy.ts +322 -0
- package/src/cli/migrate.ts +256 -3
- package/src/cli/sync-from-mcp.ts +23 -0
- package/src/cli/test.ts +10 -2
- package/src/generators/claude-code/index.ts +143 -0
- package/src/generators/codex/index.ts +23 -0
- package/src/index.ts +12 -1
- package/src/mcp/introspect.ts +297 -24
- package/src/permissions.ts +3 -1
- package/src/validation/platform-rules.ts +121 -0
package/src/cli/index.ts
CHANGED
|
@@ -13,10 +13,12 @@ import {
|
|
|
13
13
|
runAgentPlan,
|
|
14
14
|
type AgentPromptKind,
|
|
15
15
|
type AgentRunner,
|
|
16
|
+
type AgentRunnerModelSummary,
|
|
16
17
|
} from './agent'
|
|
17
|
-
import { doctorProject, printDoctorReport } from './doctor'
|
|
18
|
+
import { doctorConsumer, doctorProject, printDoctorReport } from './doctor'
|
|
18
19
|
import {
|
|
19
20
|
ensureHookTrust,
|
|
21
|
+
getInstallFollowupNotes,
|
|
20
22
|
installPlugin,
|
|
21
23
|
listHookCommands,
|
|
22
24
|
planInstallPlugin,
|
|
@@ -29,6 +31,7 @@ import {
|
|
|
29
31
|
analyzeMcpQuality,
|
|
30
32
|
applyMcpScaffoldPlan,
|
|
31
33
|
buildToolExampleRequest,
|
|
34
|
+
deriveDisplayName,
|
|
32
35
|
derivePluginName,
|
|
33
36
|
MCP_HOOK_MODES,
|
|
34
37
|
MCP_RUNTIME_AUTH_MODES,
|
|
@@ -42,18 +45,26 @@ import {
|
|
|
42
45
|
writeMcpScaffold,
|
|
43
46
|
} from './init-from-mcp'
|
|
44
47
|
import { migrate } from './migrate'
|
|
48
|
+
import { runMcpProxy } from './mcp-proxy'
|
|
45
49
|
import { lintProject, printLintResult, runLint } from './lint'
|
|
46
|
-
import {
|
|
50
|
+
import {
|
|
51
|
+
discoverMcpAuthFromError,
|
|
52
|
+
introspectMcpServer,
|
|
53
|
+
McpIntrospectionError,
|
|
54
|
+
type IntrospectedMcpServer,
|
|
55
|
+
} from '../mcp/introspect'
|
|
47
56
|
import { promptText, promptYesNo, PromptCancelledError } from './prompt'
|
|
48
57
|
import * as clack from '@clack/prompts'
|
|
49
58
|
import type { McpAuth, McpServer, TargetPlatform } from '../schema'
|
|
50
|
-
import { basename } from 'path'
|
|
59
|
+
import { basename, resolve } from 'path'
|
|
51
60
|
import { mkdir, mkdtemp, rm } from 'fs/promises'
|
|
52
61
|
import { tmpdir } from 'os'
|
|
62
|
+
import { spawn } from 'child_process'
|
|
53
63
|
import { formatSyncSummary, planSyncFromMcp, syncFromMcp } from './sync-from-mcp'
|
|
54
64
|
import { formatPublishPlan, planPublish, runPublish } from './publish'
|
|
55
|
-
import { createCliRuntime, createSpinner, printJson, readMultiValueOption, readOption } from './runtime'
|
|
65
|
+
import { createCliRuntime, createSpinner, printJson, readFlag, readMultiValueOption, readOption } from './runtime'
|
|
56
66
|
import { printTestResult, runTestSuite, type TestRunResult } from './test'
|
|
67
|
+
import { printEvalReport, runEvalSuite } from './eval'
|
|
57
68
|
|
|
58
69
|
const args = process.argv.slice(2)
|
|
59
70
|
const command = args[0]
|
|
@@ -86,6 +97,7 @@ export interface InitFromMcpOptions {
|
|
|
86
97
|
authHeader?: string
|
|
87
98
|
authTemplate?: string
|
|
88
99
|
runtimeAuth?: string
|
|
100
|
+
oauthWrapper: boolean
|
|
89
101
|
grouping?: string
|
|
90
102
|
hooks?: string
|
|
91
103
|
transport?: string
|
|
@@ -122,6 +134,7 @@ interface AutopilotSummary {
|
|
|
122
134
|
source: string
|
|
123
135
|
mode: AutopilotMode
|
|
124
136
|
runner: AgentRunner
|
|
137
|
+
model: AgentRunnerModelSummary
|
|
125
138
|
targets: TargetPlatform[]
|
|
126
139
|
toolCount: number
|
|
127
140
|
grouping: McpSkillGrouping
|
|
@@ -204,6 +217,9 @@ export async function main() {
|
|
|
204
217
|
case 'agent':
|
|
205
218
|
await runAgent()
|
|
206
219
|
break
|
|
220
|
+
case 'mcp':
|
|
221
|
+
await runMcp()
|
|
222
|
+
break
|
|
207
223
|
case 'autopilot':
|
|
208
224
|
await runAutopilot()
|
|
209
225
|
break
|
|
@@ -228,6 +244,9 @@ export async function main() {
|
|
|
228
244
|
case 'test':
|
|
229
245
|
await runTestCommand()
|
|
230
246
|
break
|
|
247
|
+
case 'eval':
|
|
248
|
+
await runEvalCommand()
|
|
249
|
+
break
|
|
231
250
|
case undefined:
|
|
232
251
|
case 'help':
|
|
233
252
|
case '--help':
|
|
@@ -349,6 +368,59 @@ function formatDuration(durationMs?: number): string | undefined {
|
|
|
349
368
|
return `${(durationMs / 1000).toFixed(durationMs >= 10_000 ? 0 : 1)}s`
|
|
350
369
|
}
|
|
351
370
|
|
|
371
|
+
interface InstallActionSummary {
|
|
372
|
+
enabled: boolean
|
|
373
|
+
platforms: TargetPlatform[]
|
|
374
|
+
notes: string[]
|
|
375
|
+
installTargets: Array<{
|
|
376
|
+
platform: TargetPlatform
|
|
377
|
+
pluginDir: string
|
|
378
|
+
built: boolean
|
|
379
|
+
existing: boolean
|
|
380
|
+
}>
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function maybeInstallBuiltOutputs(
|
|
384
|
+
config: Awaited<ReturnType<typeof loadConfig>>,
|
|
385
|
+
platforms: TargetPlatform[],
|
|
386
|
+
): Promise<InstallActionSummary | undefined> {
|
|
387
|
+
if (!args.includes('--install')) {
|
|
388
|
+
return undefined
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const distDir = `${process.cwd()}/${config.outDir}`
|
|
392
|
+
const installPlan = planInstallPlugin(distDir, config.name, platforms)
|
|
393
|
+
|
|
394
|
+
if (!runtime.dryRun) {
|
|
395
|
+
await ensureHookTrust({
|
|
396
|
+
pluginName: config.name,
|
|
397
|
+
hooks: config.hooks,
|
|
398
|
+
trust: args.includes('--trust'),
|
|
399
|
+
isTTY: runtime.isInteractive,
|
|
400
|
+
})
|
|
401
|
+
const resolvedUserConfig = await resolveInstallUserConfig(config, platforms, {
|
|
402
|
+
isTTY: runtime.isInteractive,
|
|
403
|
+
})
|
|
404
|
+
await installPlugin(distDir, config.name, platforms, {
|
|
405
|
+
config,
|
|
406
|
+
quiet: true,
|
|
407
|
+
resolvedUserConfig,
|
|
408
|
+
})
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
enabled: true,
|
|
413
|
+
platforms,
|
|
414
|
+
notes: getInstallFollowupNotes(platforms),
|
|
415
|
+
installTargets: installPlan.map((target) => ({
|
|
416
|
+
platform: target.platform,
|
|
417
|
+
pluginDir: target.description,
|
|
418
|
+
built: target.built,
|
|
419
|
+
existing: target.existing,
|
|
420
|
+
})),
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
352
424
|
function logAutopilotRunnerWait(step: number, totalSteps: number, label: string, runner: AgentRunner): void {
|
|
353
425
|
if (runtime.jsonOutput || runtime.quiet || !runtime.isInteractive) {
|
|
354
426
|
return
|
|
@@ -393,13 +465,17 @@ async function runBuild() {
|
|
|
393
465
|
const targets = parseTargetFlagValues(args)
|
|
394
466
|
const config = await loadConfig()
|
|
395
467
|
const platforms = targets ?? config.targets
|
|
468
|
+
const cwd = process.cwd()
|
|
469
|
+
const shouldInstall = args.includes('--install')
|
|
396
470
|
|
|
397
471
|
if (runtime.dryRun) {
|
|
472
|
+
const install = await maybeInstallBuiltOutputs(config, platforms)
|
|
398
473
|
const summary = {
|
|
399
474
|
dryRun: true,
|
|
400
475
|
targets: platforms,
|
|
401
476
|
outDir: config.outDir,
|
|
402
477
|
outputPaths: platforms.map((platform) => `${config.outDir}/${platform}/`),
|
|
478
|
+
install,
|
|
403
479
|
}
|
|
404
480
|
if (runtime.jsonOutput) {
|
|
405
481
|
printJson(summary)
|
|
@@ -414,7 +490,27 @@ async function runBuild() {
|
|
|
414
490
|
console.log(`Building for: ${platforms.join(', ')}`)
|
|
415
491
|
}
|
|
416
492
|
|
|
417
|
-
await
|
|
493
|
+
const lintResult = await lintProject(cwd, { targets: platforms })
|
|
494
|
+
if (lintResult.errors > 0) {
|
|
495
|
+
if (runtime.jsonOutput) {
|
|
496
|
+
printJson({
|
|
497
|
+
ok: false,
|
|
498
|
+
reason: 'lint-errors',
|
|
499
|
+
lint: lintResult,
|
|
500
|
+
})
|
|
501
|
+
} else {
|
|
502
|
+
printLintResult(lintResult, cwd)
|
|
503
|
+
console.error('Build aborted due to lint errors.')
|
|
504
|
+
}
|
|
505
|
+
process.exit(1)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (!runtime.jsonOutput && !runtime.quiet && lintResult.warnings > 0) {
|
|
509
|
+
printLintResult(lintResult, cwd)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
await build(config, cwd, { targets })
|
|
513
|
+
const install = await maybeInstallBuiltOutputs(config, platforms)
|
|
418
514
|
|
|
419
515
|
if (runtime.jsonOutput) {
|
|
420
516
|
printJson({
|
|
@@ -422,6 +518,8 @@ async function runBuild() {
|
|
|
422
518
|
targets: platforms,
|
|
423
519
|
outDir: config.outDir,
|
|
424
520
|
outputPaths: platforms.map((platform) => `${config.outDir}/${platform}/`),
|
|
521
|
+
lint: lintResult,
|
|
522
|
+
install,
|
|
425
523
|
})
|
|
426
524
|
return
|
|
427
525
|
}
|
|
@@ -431,6 +529,15 @@ async function runBuild() {
|
|
|
431
529
|
for (const platform of platforms) {
|
|
432
530
|
console.log(` ${config.outDir}/${platform}/`)
|
|
433
531
|
}
|
|
532
|
+
if (shouldInstall && install) {
|
|
533
|
+
console.log('Installed for local testing:')
|
|
534
|
+
for (const target of install.installTargets) {
|
|
535
|
+
console.log(` ${target.platform} -> ${target.pluginDir}`)
|
|
536
|
+
}
|
|
537
|
+
for (const note of install.notes) {
|
|
538
|
+
console.log(note)
|
|
539
|
+
}
|
|
540
|
+
}
|
|
434
541
|
}
|
|
435
542
|
}
|
|
436
543
|
|
|
@@ -561,9 +668,120 @@ function defaultHookMode(source: { auth?: { type: string; envVar?: string }; tra
|
|
|
561
668
|
return 'none'
|
|
562
669
|
}
|
|
563
670
|
|
|
564
|
-
|
|
671
|
+
interface McpAuthProviderHint {
|
|
672
|
+
id: 'linear' | 'generic-oauth'
|
|
673
|
+
label: string
|
|
674
|
+
envVar: string
|
|
675
|
+
tokenLabel: string
|
|
676
|
+
tokenAuthType: 'bearer' | 'header'
|
|
677
|
+
authHeader?: string
|
|
678
|
+
authTemplate?: string
|
|
679
|
+
wrapperCommand?: string
|
|
680
|
+
guidance?: string
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function getSourceUrl(source: McpServer): string | undefined {
|
|
684
|
+
return source.transport === 'stdio' ? undefined : source.url
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function collectAuthCandidateUrls(
|
|
688
|
+
source: McpServer,
|
|
689
|
+
discoveredAuth?: ReturnType<typeof discoverMcpAuthFromError> | null,
|
|
690
|
+
): string[] {
|
|
691
|
+
return [
|
|
692
|
+
getSourceUrl(source),
|
|
693
|
+
discoveredAuth?.authorizationUrl,
|
|
694
|
+
discoveredAuth?.resourceMetadataUrl,
|
|
695
|
+
].filter((value): value is string => Boolean(value))
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function matchesHost(urlValue: string, suffix: string): boolean {
|
|
699
|
+
try {
|
|
700
|
+
return new URL(urlValue).hostname.endsWith(suffix)
|
|
701
|
+
} catch {
|
|
702
|
+
return false
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
export function buildOauthWrapperCommand(source: McpServer): string | undefined {
|
|
707
|
+
if (source.transport !== 'http') return undefined
|
|
708
|
+
return `npx -y mcp-remote ${source.url}`
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
export function buildOauthWrapperSource(source: McpServer): McpServer | undefined {
|
|
712
|
+
if (source.transport !== 'http') return undefined
|
|
713
|
+
return {
|
|
714
|
+
transport: 'stdio',
|
|
715
|
+
command: 'npx',
|
|
716
|
+
args: ['-y', 'mcp-remote', source.url],
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export function inferMcpAuthProvider(
|
|
721
|
+
source: McpServer,
|
|
722
|
+
discoveredAuth?: ReturnType<typeof discoverMcpAuthFromError> | null,
|
|
723
|
+
): McpAuthProviderHint | null {
|
|
724
|
+
const candidates = collectAuthCandidateUrls(source, discoveredAuth)
|
|
725
|
+
if (candidates.some((value) => matchesHost(value, 'linear.app'))) {
|
|
726
|
+
return {
|
|
727
|
+
id: 'linear',
|
|
728
|
+
label: 'Linear',
|
|
729
|
+
envVar: 'LINEAR_API_KEY',
|
|
730
|
+
tokenLabel: 'API key or OAuth token',
|
|
731
|
+
tokenAuthType: 'bearer',
|
|
732
|
+
authTemplate: 'Bearer ${value}',
|
|
733
|
+
wrapperCommand: buildOauthWrapperCommand(source),
|
|
734
|
+
guidance: 'Linear supports direct Authorization: Bearer tokens/API keys and the official mcp-remote wrapper for clients that do not support remote MCP.',
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (discoveredAuth?.kind === 'platform') {
|
|
739
|
+
return {
|
|
740
|
+
id: 'generic-oauth',
|
|
741
|
+
label: 'OAuth-first MCP',
|
|
742
|
+
envVar: 'OAUTH_ACCESS_TOKEN',
|
|
743
|
+
tokenLabel: 'access token or API key',
|
|
744
|
+
tokenAuthType: 'bearer',
|
|
745
|
+
authTemplate: 'Bearer ${value}',
|
|
746
|
+
wrapperCommand: buildOauthWrapperCommand(source),
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return null
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function defaultAuthEnvVar(
|
|
754
|
+
provider: McpAuthProviderHint | null,
|
|
755
|
+
discoveredAuth?: ReturnType<typeof discoverMcpAuthFromError> | null,
|
|
756
|
+
): string {
|
|
757
|
+
if (provider?.envVar) return provider.envVar
|
|
758
|
+
if (discoveredAuth?.kind === 'header') return 'API_KEY'
|
|
759
|
+
if (discoveredAuth?.kind === 'platform') return 'OAUTH_ACCESS_TOKEN'
|
|
760
|
+
return ''
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function tryOpenBrowser(url: string): boolean {
|
|
764
|
+
const launcher = process.platform === 'darwin'
|
|
765
|
+
? { command: 'open', args: [url] }
|
|
766
|
+
: process.platform === 'win32'
|
|
767
|
+
? { command: 'cmd', args: ['/c', 'start', '', url] }
|
|
768
|
+
: { command: 'xdg-open', args: [url] }
|
|
769
|
+
|
|
770
|
+
try {
|
|
771
|
+
const child = spawn(launcher.command, launcher.args, {
|
|
772
|
+
detached: true,
|
|
773
|
+
stdio: 'ignore',
|
|
774
|
+
})
|
|
775
|
+
child.unref()
|
|
776
|
+
return true
|
|
777
|
+
} catch {
|
|
778
|
+
return false
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
export function resolveRemoteAuthType(options: Pick<InitFromMcpOptions, 'authType' | 'authHeader'>): 'bearer' | 'header' | 'platform' {
|
|
565
783
|
if (options.authType) {
|
|
566
|
-
return parseChoiceOption(options.authType, ['bearer', 'header'] as const, 'Auth type')
|
|
784
|
+
return parseChoiceOption(options.authType, ['bearer', 'header', 'platform'] as const, 'Auth type')
|
|
567
785
|
}
|
|
568
786
|
|
|
569
787
|
if (options.authHeader && options.authHeader.trim() && options.authHeader.trim().toLowerCase() !== 'authorization') {
|
|
@@ -574,11 +792,25 @@ export function resolveRemoteAuthType(options: Pick<InitFromMcpOptions, 'authTyp
|
|
|
574
792
|
}
|
|
575
793
|
|
|
576
794
|
export function buildRemoteAuthConfig(options: Pick<InitFromMcpOptions, 'authEnv' | 'authType' | 'authHeader' | 'authTemplate'>): McpAuth | undefined {
|
|
795
|
+
if (options.authType?.trim() === 'platform') {
|
|
796
|
+
return {
|
|
797
|
+
type: 'platform',
|
|
798
|
+
mode: 'oauth',
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
577
802
|
const envVar = options.authEnv?.trim()
|
|
578
803
|
if (!envVar) return undefined
|
|
579
804
|
|
|
580
805
|
const authType = resolveRemoteAuthType(options)
|
|
581
806
|
|
|
807
|
+
if (authType === 'platform') {
|
|
808
|
+
return {
|
|
809
|
+
type: 'platform',
|
|
810
|
+
mode: 'oauth',
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
582
814
|
if (authType === 'header') {
|
|
583
815
|
const headerName = options.authHeader?.trim()
|
|
584
816
|
if (!headerName) {
|
|
@@ -657,40 +889,82 @@ function isAuthRequiredError(error: unknown): error is McpIntrospectionError {
|
|
|
657
889
|
}
|
|
658
890
|
|
|
659
891
|
function isLikelyOAuthFirstError(error: McpIntrospectionError): boolean {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|| responseUrl.includes('authorize')
|
|
674
|
-
|| responseUrl.includes('login')
|
|
675
|
-
|| body.includes('oauth')
|
|
676
|
-
|| body.includes('authorize')
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
function formatAuthRequiredMessage(commandName: 'init' | 'autopilot', error?: McpIntrospectionError): string {
|
|
680
|
-
const rerun = `Re-run ${commandName} with --auth-env YOUR_ENV_VAR and either:
|
|
892
|
+
return discoverMcpAuthFromError(error)?.kind === 'platform'
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function formatAuthRequiredMessage(
|
|
896
|
+
commandName: 'init' | 'autopilot',
|
|
897
|
+
error?: McpIntrospectionError,
|
|
898
|
+
source?: McpServer,
|
|
899
|
+
): string {
|
|
900
|
+
const discoveredAuth = error ? discoverMcpAuthFromError(error) : null
|
|
901
|
+
const provider = source ? inferMcpAuthProvider(source, discoveredAuth) : null
|
|
902
|
+
const rerun = discoveredAuth?.kind === 'header' && discoveredAuth.headerName
|
|
903
|
+
? `Re-run ${commandName} with --auth-env YOUR_ENV_VAR --auth-type header --auth-header ${discoveredAuth.headerName} [--auth-template '\${value}']`
|
|
904
|
+
: `Re-run ${commandName} with --auth-env YOUR_ENV_VAR and either:
|
|
681
905
|
- Bearer auth: --auth-type bearer
|
|
682
906
|
- Custom header auth: --auth-type header --auth-header HEADER_NAME [--auth-template '\${value}']`
|
|
683
|
-
const
|
|
907
|
+
const providerNote = provider?.guidance ? `\n\n${provider.guidance}` : ''
|
|
908
|
+
const wrapperNote = provider?.wrapperCommand
|
|
909
|
+
? `\nLocal wrapper/proxy helper: ${provider.wrapperCommand}`
|
|
910
|
+
: ''
|
|
911
|
+
const oauthNote = discoveredAuth?.kind === 'platform'
|
|
684
912
|
? `
|
|
685
913
|
|
|
686
|
-
This server appears OAuth-first. Complete the provider's OAuth flow first, export the resulting token/API key to YOUR_ENV_VAR, then rerun.
|
|
687
|
-
If
|
|
914
|
+
This server appears OAuth-first${discoveredAuth.authorizationUrl ? ` (${discoveredAuth.authorizationUrl})` : ''}. Complete the provider's OAuth flow first, export the resulting token/API key to YOUR_ENV_VAR, then rerun.
|
|
915
|
+
If the server supports public discovery and only needs OAuth at runtime, you can scaffold it with --auth-type platform --runtime-auth platform.
|
|
916
|
+
If it requires browser-interactive OAuth during handshake, run a local stdio MCP wrapper/proxy and import that command instead.${wrapperNote}${providerNote}`
|
|
688
917
|
: ''
|
|
689
918
|
|
|
690
919
|
return `This MCP server requires authentication.
|
|
691
920
|
${rerun}${oauthNote}`
|
|
692
921
|
}
|
|
693
922
|
|
|
923
|
+
type OAuthImportStrategy = 'token' | 'wrapper'
|
|
924
|
+
|
|
925
|
+
async function chooseOAuthImportStrategy(
|
|
926
|
+
provider: McpAuthProviderHint | null,
|
|
927
|
+
): Promise<OAuthImportStrategy> {
|
|
928
|
+
const options: Array<{ value: OAuthImportStrategy; label: string; hint?: string }> = [
|
|
929
|
+
{
|
|
930
|
+
value: 'token',
|
|
931
|
+
label: 'token',
|
|
932
|
+
hint: `Complete auth in the browser, then continue with a ${provider?.tokenLabel ?? 'token or API key'}`,
|
|
933
|
+
},
|
|
934
|
+
]
|
|
935
|
+
|
|
936
|
+
if (provider?.wrapperCommand) {
|
|
937
|
+
options.push({
|
|
938
|
+
value: 'wrapper',
|
|
939
|
+
label: 'wrapper',
|
|
940
|
+
hint: `Import through ${provider.wrapperCommand}`,
|
|
941
|
+
})
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
return await clackSelect('OAuth import path', options, provider?.wrapperCommand ? 'wrapper' : 'token')
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
async function maybeCaptureOAuthCredential(
|
|
948
|
+
provider: McpAuthProviderHint | null,
|
|
949
|
+
authorizationUrl?: string,
|
|
950
|
+
): Promise<string | undefined> {
|
|
951
|
+
if (authorizationUrl) {
|
|
952
|
+
const opened = tryOpenBrowser(authorizationUrl)
|
|
953
|
+
const message = opened
|
|
954
|
+
? `Opened ${authorizationUrl} in your browser. Finish the auth flow, then paste the resulting ${provider?.tokenLabel ?? 'token or API key'} below if you want Pluxx to retry immediately.`
|
|
955
|
+
: `Open ${authorizationUrl} in your browser. Finish the auth flow, then paste the resulting ${provider?.tokenLabel ?? 'token or API key'} below if you want Pluxx to retry immediately.`
|
|
956
|
+
clack.note(message, 'OAuth flow')
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
const credential = (await clackPassword(`Paste ${provider?.tokenLabel ?? 'token or API key'} for this session (optional)`)).trim()
|
|
960
|
+
return credential || undefined
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function applySessionCredential(envVar: string | undefined, credential: string | undefined) {
|
|
964
|
+
if (!envVar || !credential) return
|
|
965
|
+
process.env[envVar] = credential
|
|
966
|
+
}
|
|
967
|
+
|
|
694
968
|
function buildInitSummary(input: {
|
|
695
969
|
pluginName: string
|
|
696
970
|
displayName: string
|
|
@@ -765,6 +1039,21 @@ function formatMcpQualityLines(report: McpQualityReport): string[] {
|
|
|
765
1039
|
return lines
|
|
766
1040
|
}
|
|
767
1041
|
|
|
1042
|
+
function formatMcpDiscoverySummary(introspection: IntrospectedMcpServer): string {
|
|
1043
|
+
const parts = [`${introspection.tools.length} tools`]
|
|
1044
|
+
const resourceCount = (introspection.resources?.length ?? 0) + (introspection.resourceTemplates?.length ?? 0)
|
|
1045
|
+
const promptCount = introspection.prompts?.length ?? 0
|
|
1046
|
+
|
|
1047
|
+
if (resourceCount > 0) {
|
|
1048
|
+
parts.push(`${resourceCount} resources`)
|
|
1049
|
+
}
|
|
1050
|
+
if (promptCount > 0) {
|
|
1051
|
+
parts.push(`${promptCount} prompts`)
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
return `${parts.join(', ')} discovered`
|
|
1055
|
+
}
|
|
1056
|
+
|
|
768
1057
|
export function parseInitFromMcpOptions(rawArgs: string[], initialName?: string, initialSource?: string): InitFromMcpOptions {
|
|
769
1058
|
return {
|
|
770
1059
|
source: initialSource ?? readOption(rawArgs, '--from-mcp'),
|
|
@@ -778,6 +1067,7 @@ export function parseInitFromMcpOptions(rawArgs: string[], initialName?: string,
|
|
|
778
1067
|
authHeader: readOption(rawArgs, '--auth-header'),
|
|
779
1068
|
authTemplate: readOption(rawArgs, '--auth-template'),
|
|
780
1069
|
runtimeAuth: readOption(rawArgs, '--runtime-auth'),
|
|
1070
|
+
oauthWrapper: rawArgs.includes('--oauth-wrapper'),
|
|
781
1071
|
grouping: readOption(rawArgs, '--grouping'),
|
|
782
1072
|
hooks: readOption(rawArgs, '--hooks'),
|
|
783
1073
|
transport: readOption(rawArgs, '--transport'),
|
|
@@ -978,6 +1268,7 @@ async function runInitFromMcp(initialName?: string, initialSource?: string) {
|
|
|
978
1268
|
}
|
|
979
1269
|
|
|
980
1270
|
let source = parseMcpSourceInput(rawSource, options.transport)
|
|
1271
|
+
let introspectionSource = source
|
|
981
1272
|
const configuredRemoteAuth = source.transport === 'stdio'
|
|
982
1273
|
? undefined
|
|
983
1274
|
: buildRemoteAuthConfig(options)
|
|
@@ -993,55 +1284,114 @@ async function runInitFromMcp(initialName?: string, initialSource?: string) {
|
|
|
993
1284
|
|
|
994
1285
|
let introspection
|
|
995
1286
|
try {
|
|
996
|
-
introspection = await introspectMcpServer(
|
|
1287
|
+
introspection = await introspectMcpServer(introspectionSource)
|
|
997
1288
|
} catch (error) {
|
|
998
1289
|
if (source.transport !== 'stdio' && isAuthRequiredError(error)) {
|
|
1290
|
+
const discoveredAuth = error instanceof McpIntrospectionError ? discoverMcpAuthFromError(error) : null
|
|
1291
|
+
const provider = inferMcpAuthProvider(source, discoveredAuth)
|
|
999
1292
|
s?.stop('Server requires authentication')
|
|
1000
|
-
|
|
1001
|
-
? await clackText('Auth env var for this MCP server')
|
|
1002
|
-
: '')
|
|
1003
|
-
if (!envVar) {
|
|
1004
|
-
throw new Error(formatAuthRequiredMessage('init', error))
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1293
|
+
let envVar = options.authEnv
|
|
1007
1294
|
let authType = options.authType
|
|
1008
1295
|
let authHeader = options.authHeader
|
|
1009
1296
|
let authTemplate = options.authTemplate
|
|
1297
|
+
let usedOauthWrapper = false
|
|
1298
|
+
|
|
1299
|
+
if (!usedOauthWrapper && discoveredAuth?.kind === 'platform' && (interactive || options.oauthWrapper)) {
|
|
1300
|
+
const strategy = options.oauthWrapper
|
|
1301
|
+
? 'wrapper'
|
|
1302
|
+
: interactive
|
|
1303
|
+
? await chooseOAuthImportStrategy(provider)
|
|
1304
|
+
: 'token'
|
|
1305
|
+
|
|
1306
|
+
if (strategy === 'wrapper') {
|
|
1307
|
+
const wrapperSource = buildOauthWrapperSource(source)
|
|
1308
|
+
if (!wrapperSource) {
|
|
1309
|
+
throw new Error(formatAuthRequiredMessage('init', error, source))
|
|
1310
|
+
}
|
|
1311
|
+
introspectionSource = wrapperSource
|
|
1312
|
+
runtimeAuthMode = 'platform'
|
|
1313
|
+
s?.start('Step 1/4 · Reconnecting via local wrapper...')
|
|
1314
|
+
try {
|
|
1315
|
+
introspection = await introspectMcpServer(introspectionSource)
|
|
1316
|
+
usedOauthWrapper = true
|
|
1317
|
+
} catch (wrapperError) {
|
|
1318
|
+
if (isAuthRequiredError(wrapperError)) {
|
|
1319
|
+
throw new Error(`Wrapper-based OAuth import failed.
|
|
1320
|
+
${formatAuthRequiredMessage('init', wrapperError, source)}`)
|
|
1321
|
+
}
|
|
1322
|
+
throw new Error(`MCP introspection failed via local wrapper: ${wrapperError instanceof Error ? wrapperError.message : String(wrapperError)}`)
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1010
1326
|
|
|
1011
|
-
if (
|
|
1012
|
-
|
|
1013
|
-
{ value: 'bearer', label: 'bearer', hint: 'Authorization: Bearer <token>' },
|
|
1014
|
-
{ value: 'header', label: 'header', hint: 'Custom header such as X-API-Key' },
|
|
1015
|
-
], 'bearer')
|
|
1327
|
+
if (!options.runtimeAuth && (discoveredAuth?.kind === 'platform' || usedOauthWrapper)) {
|
|
1328
|
+
runtimeAuthMode = 'platform'
|
|
1016
1329
|
}
|
|
1017
1330
|
|
|
1018
|
-
if (
|
|
1019
|
-
|
|
1020
|
-
|
|
1331
|
+
if (usedOauthWrapper) {
|
|
1332
|
+
envVar = envVar ?? (interactive
|
|
1333
|
+
? await clackText('Auth env var for generated non-platform targets (optional)', defaultAuthEnvVar(provider, discoveredAuth))
|
|
1334
|
+
: undefined)
|
|
1335
|
+
source = {
|
|
1336
|
+
...source,
|
|
1337
|
+
auth: envVar
|
|
1338
|
+
? buildRemoteAuthConfig({
|
|
1339
|
+
authEnv: envVar,
|
|
1340
|
+
authType: provider?.tokenAuthType ?? 'bearer',
|
|
1341
|
+
authHeader: provider?.authHeader,
|
|
1342
|
+
authTemplate: provider?.authTemplate,
|
|
1343
|
+
})
|
|
1344
|
+
: { type: 'platform', mode: 'oauth' },
|
|
1021
1345
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1346
|
+
} else {
|
|
1347
|
+
envVar = envVar ?? (interactive
|
|
1348
|
+
? await clackText('Auth env var for this MCP server', defaultAuthEnvVar(provider, discoveredAuth))
|
|
1349
|
+
: '')
|
|
1350
|
+
if (!envVar) {
|
|
1351
|
+
throw new Error(formatAuthRequiredMessage('init', error, source))
|
|
1024
1352
|
}
|
|
1025
|
-
}
|
|
1026
1353
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1354
|
+
if (interactive && discoveredAuth?.kind === 'platform') {
|
|
1355
|
+
const credential = await maybeCaptureOAuthCredential(provider, discoveredAuth.authorizationUrl)
|
|
1356
|
+
applySessionCredential(envVar, credential)
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (interactive && !authType) {
|
|
1360
|
+
authType = await clackSelect<'bearer' | 'header'>('Auth type', [
|
|
1361
|
+
{ value: 'bearer', label: 'bearer', hint: 'Authorization: Bearer <token>' },
|
|
1362
|
+
{ value: 'header', label: 'header', hint: 'Custom header such as X-API-Key' },
|
|
1363
|
+
], discoveredAuth?.kind === 'header' ? 'header' : (provider?.tokenAuthType ?? 'bearer'))
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
if (resolveRemoteAuthType({ authType, authHeader }) === 'header') {
|
|
1367
|
+
if (interactive && !authHeader) {
|
|
1368
|
+
authHeader = await clackText('Auth header name', discoveredAuth?.headerName ?? provider?.authHeader ?? 'X-API-Key')
|
|
1369
|
+
}
|
|
1370
|
+
if (interactive && !authTemplate) {
|
|
1371
|
+
authTemplate = await clackText('Auth header template', provider?.authTemplate ?? '${value}')
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
source = {
|
|
1376
|
+
...source,
|
|
1377
|
+
auth: buildRemoteAuthConfig({
|
|
1378
|
+
authEnv: envVar,
|
|
1379
|
+
authType,
|
|
1380
|
+
authHeader,
|
|
1381
|
+
authTemplate,
|
|
1382
|
+
}),
|
|
1383
|
+
}
|
|
1384
|
+
introspectionSource = source
|
|
1385
|
+
s?.start('Step 1/4 · Reconnecting with auth...')
|
|
1386
|
+
try {
|
|
1387
|
+
introspection = await introspectMcpServer(introspectionSource)
|
|
1388
|
+
} catch (retryError) {
|
|
1389
|
+
if (isAuthRequiredError(retryError)) {
|
|
1390
|
+
throw new Error(`Authentication failed after retry.
|
|
1391
|
+
${formatAuthRequiredMessage('init', retryError, source)}`)
|
|
1392
|
+
}
|
|
1393
|
+
throw new Error(`MCP introspection failed after auth retry: ${retryError instanceof Error ? retryError.message : String(retryError)}`)
|
|
1043
1394
|
}
|
|
1044
|
-
throw new Error(`MCP introspection failed after auth retry: ${retryError instanceof Error ? retryError.message : String(retryError)}`)
|
|
1045
1395
|
}
|
|
1046
1396
|
} else {
|
|
1047
1397
|
s?.stop('Connection failed')
|
|
@@ -1049,8 +1399,12 @@ ${formatAuthRequiredMessage('init', retryError)}`)
|
|
|
1049
1399
|
}
|
|
1050
1400
|
}
|
|
1051
1401
|
|
|
1402
|
+
if (!introspection) {
|
|
1403
|
+
throw new Error('MCP introspection did not return server metadata.')
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1052
1406
|
const serverLabel = introspection.serverInfo.title ?? introspection.serverInfo.name
|
|
1053
|
-
s?.stop(`Connected: ${serverLabel} (${introspection
|
|
1407
|
+
s?.stop(`Connected: ${serverLabel} (${formatMcpDiscoverySummary(introspection)})`)
|
|
1054
1408
|
const quality = analyzeMcpQuality(introspection.tools)
|
|
1055
1409
|
|
|
1056
1410
|
if (!options.jsonOutput && !runtime.quiet && quality.issues.length > 0) {
|
|
@@ -1081,6 +1435,7 @@ ${formatAuthRequiredMessage('init', retryError)}`)
|
|
|
1081
1435
|
&& source.auth.type !== 'none'
|
|
1082
1436
|
&& !options.runtimeAuth
|
|
1083
1437
|
) {
|
|
1438
|
+
runtimeAuthMode = source.auth.type === 'platform' ? 'platform' : runtimeAuthMode
|
|
1084
1439
|
runtimeAuthMode = await clackSelect<McpRuntimeAuthMode>('Claude/Cursor runtime auth', [
|
|
1085
1440
|
{ value: 'inline', label: 'inline', hint: 'Generate env/header auth directly into plugin output' },
|
|
1086
1441
|
{ value: 'platform', label: 'platform', hint: 'Use native platform-managed auth (for example OAuth/custom connector flows)' },
|
|
@@ -1099,7 +1454,7 @@ ${formatAuthRequiredMessage('init', retryError)}`)
|
|
|
1099
1454
|
? await clackText('Plugin name', defaultPluginName)
|
|
1100
1455
|
: defaultPluginName),
|
|
1101
1456
|
)
|
|
1102
|
-
const defaultDisplayName = options.displayName ?? introspection
|
|
1457
|
+
const defaultDisplayName = options.displayName ?? deriveDisplayName(introspection, pluginName)
|
|
1103
1458
|
const displayName = options.displayName ?? (interactive
|
|
1104
1459
|
? await clackText('Display name', defaultDisplayName)
|
|
1105
1460
|
: defaultDisplayName)
|
|
@@ -1294,6 +1649,18 @@ async function clackText(message: string, defaultValue?: string): Promise<string
|
|
|
1294
1649
|
return result
|
|
1295
1650
|
}
|
|
1296
1651
|
|
|
1652
|
+
/** Wrapper for clack.password that handles cancellation. */
|
|
1653
|
+
async function clackPassword(message: string): Promise<string> {
|
|
1654
|
+
const result = await clack.password({
|
|
1655
|
+
message,
|
|
1656
|
+
mask: '*',
|
|
1657
|
+
})
|
|
1658
|
+
if (clack.isCancel(result)) {
|
|
1659
|
+
throw new PromptCancelledError()
|
|
1660
|
+
}
|
|
1661
|
+
return result
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1297
1664
|
/** Wrapper for clack.select that handles cancellation. */
|
|
1298
1665
|
async function clackSelect<T extends string>(
|
|
1299
1666
|
message: string,
|
|
@@ -1347,7 +1714,12 @@ async function runSync() {
|
|
|
1347
1714
|
}
|
|
1348
1715
|
|
|
1349
1716
|
async function runDoctor() {
|
|
1350
|
-
const
|
|
1717
|
+
const consumerMode = readFlag(args, '--consumer')
|
|
1718
|
+
const doctorPath = args.slice(1).find((value) => !value.startsWith('-'))
|
|
1719
|
+
const rootDir = doctorPath ? resolve(process.cwd(), doctorPath) : process.cwd()
|
|
1720
|
+
const report = consumerMode
|
|
1721
|
+
? await doctorConsumer(rootDir)
|
|
1722
|
+
: await doctorProject(rootDir)
|
|
1351
1723
|
|
|
1352
1724
|
if (runtime.jsonOutput) {
|
|
1353
1725
|
printJson(report)
|
|
@@ -1483,6 +1855,7 @@ async function runAgent() {
|
|
|
1483
1855
|
pluginName: plan.pluginName,
|
|
1484
1856
|
kind: plan.kind,
|
|
1485
1857
|
runner: plan.runner,
|
|
1858
|
+
model: plan.model,
|
|
1486
1859
|
verify: plan.verify,
|
|
1487
1860
|
command: plan.command,
|
|
1488
1861
|
commandDisplay: plan.commandDisplay,
|
|
@@ -1500,6 +1873,7 @@ async function runAgent() {
|
|
|
1500
1873
|
} else if (!runtime.quiet) {
|
|
1501
1874
|
console.log(`Planned ${plan.kind} run for ${plan.pluginName}`)
|
|
1502
1875
|
console.log(` Runner: ${plan.runner}`)
|
|
1876
|
+
console.log(` Model: ${plan.model.display}`)
|
|
1503
1877
|
console.log(` Command: ${plan.commandDisplay}`)
|
|
1504
1878
|
}
|
|
1505
1879
|
return
|
|
@@ -1519,6 +1893,7 @@ async function runAgent() {
|
|
|
1519
1893
|
|
|
1520
1894
|
if (!runtime.quiet) {
|
|
1521
1895
|
console.log(`Completed ${result.kind} run for ${result.pluginName} via ${result.runner}`)
|
|
1896
|
+
console.log(` Model: ${result.model.display}`)
|
|
1522
1897
|
if (!verboseRunner) {
|
|
1523
1898
|
console.log(' Runner logs: suppressed (use --verbose-runner to stream)')
|
|
1524
1899
|
}
|
|
@@ -1557,12 +1932,12 @@ async function runAutopilot() {
|
|
|
1557
1932
|
let runtimeAuthMode = resolveRuntimeAuthMode(initOptions.runtimeAuth)
|
|
1558
1933
|
|
|
1559
1934
|
if (!initOptions.source && !interactive) {
|
|
1560
|
-
console.error(`Usage: pluxx autopilot --from-mcp <source> --runner <${AGENT_RUNNERS.join('|')}> [--mode <${AUTOPILOT_MODES.join('|')}>] [--name NAME] [--display-name NAME] [--author NAME] [--targets <platforms>] [--grouping workflow|tool] [--hooks none|safe] [--auth-env ENV] [--auth-type bearer|header] [--auth-header NAME] [--auth-template TEMPLATE] [--runtime-auth inline|platform] [--website URL] [--docs URL] [--context <files...>] [--review] [--no-verify] [--verbose-runner] [--json] [--dry-run] [--quiet]`)
|
|
1935
|
+
console.error(`Usage: pluxx autopilot --from-mcp <source> --runner <${AGENT_RUNNERS.join('|')}> [--mode <${AUTOPILOT_MODES.join('|')}>] [--name NAME] [--display-name NAME] [--author NAME] [--targets <platforms>] [--grouping workflow|tool] [--hooks none|safe] [--auth-env ENV] [--auth-type bearer|header|platform] [--auth-header NAME] [--auth-template TEMPLATE] [--runtime-auth inline|platform] [--oauth-wrapper] [--website URL] [--docs URL] [--context <files...>] [--review] [--no-verify] [--verbose-runner] [--json] [--dry-run] [--quiet]`)
|
|
1561
1936
|
process.exit(1)
|
|
1562
1937
|
}
|
|
1563
1938
|
|
|
1564
1939
|
if ((!runnerRaw || !AGENT_RUNNERS.includes(runnerRaw as AgentRunner)) && !interactive) {
|
|
1565
|
-
console.error(`Usage: pluxx autopilot --from-mcp <source> --runner <${AGENT_RUNNERS.join('|')}> [--mode <${AUTOPILOT_MODES.join('|')}>] [--name NAME] [--display-name NAME] [--author NAME] [--targets <platforms>] [--grouping workflow|tool] [--hooks none|safe] [--auth-env ENV] [--auth-type bearer|header] [--auth-header NAME] [--auth-template TEMPLATE] [--runtime-auth inline|platform] [--website URL] [--docs URL] [--context <files...>] [--review] [--no-verify] [--verbose-runner] [--json] [--dry-run] [--quiet]`)
|
|
1940
|
+
console.error(`Usage: pluxx autopilot --from-mcp <source> --runner <${AGENT_RUNNERS.join('|')}> [--mode <${AUTOPILOT_MODES.join('|')}>] [--name NAME] [--display-name NAME] [--author NAME] [--targets <platforms>] [--grouping workflow|tool] [--hooks none|safe] [--auth-env ENV] [--auth-type bearer|header|platform] [--auth-header NAME] [--auth-template TEMPLATE] [--runtime-auth inline|platform] [--oauth-wrapper] [--website URL] [--docs URL] [--context <files...>] [--review] [--no-verify] [--verbose-runner] [--json] [--dry-run] [--quiet]`)
|
|
1566
1941
|
process.exit(1)
|
|
1567
1942
|
}
|
|
1568
1943
|
|
|
@@ -1610,6 +1985,7 @@ async function runAutopilot() {
|
|
|
1610
1985
|
}
|
|
1611
1986
|
|
|
1612
1987
|
let source = parseMcpSourceInput(rawSource, initOptions.transport)
|
|
1988
|
+
let introspectionSource = source
|
|
1613
1989
|
const configuredRemoteAuth = source.transport === 'stdio'
|
|
1614
1990
|
? undefined
|
|
1615
1991
|
: buildRemoteAuthConfig({
|
|
@@ -1631,51 +2007,110 @@ async function runAutopilot() {
|
|
|
1631
2007
|
|
|
1632
2008
|
let introspection
|
|
1633
2009
|
try {
|
|
1634
|
-
introspection = await introspectMcpServer(
|
|
2010
|
+
introspection = await introspectMcpServer(introspectionSource)
|
|
1635
2011
|
} catch (error) {
|
|
1636
2012
|
if (source.transport !== 'stdio' && isAuthRequiredError(error)) {
|
|
2013
|
+
const discoveredAuth = error instanceof McpIntrospectionError ? discoverMcpAuthFromError(error) : null
|
|
2014
|
+
const provider = inferMcpAuthProvider(source, discoveredAuth)
|
|
1637
2015
|
connectSpinner?.stop('Server requires authentication')
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
2016
|
+
let usedOauthWrapper = false
|
|
2017
|
+
|
|
2018
|
+
if (!usedOauthWrapper && discoveredAuth?.kind === 'platform' && (interactive || initOptions.oauthWrapper)) {
|
|
2019
|
+
const strategy = initOptions.oauthWrapper
|
|
2020
|
+
? 'wrapper'
|
|
2021
|
+
: interactive
|
|
2022
|
+
? await chooseOAuthImportStrategy(provider)
|
|
2023
|
+
: 'token'
|
|
2024
|
+
|
|
2025
|
+
if (strategy === 'wrapper') {
|
|
2026
|
+
const wrapperSource = buildOauthWrapperSource(source)
|
|
2027
|
+
if (!wrapperSource) {
|
|
2028
|
+
throw new Error(formatAuthRequiredMessage('autopilot', error, source))
|
|
2029
|
+
}
|
|
2030
|
+
introspectionSource = wrapperSource
|
|
2031
|
+
runtimeAuthMode = 'platform'
|
|
2032
|
+
connectSpinner?.start('Autopilot · Reconnecting via local wrapper...')
|
|
2033
|
+
try {
|
|
2034
|
+
introspection = await introspectMcpServer(introspectionSource)
|
|
2035
|
+
usedOauthWrapper = true
|
|
2036
|
+
} catch (wrapperError) {
|
|
2037
|
+
if (isAuthRequiredError(wrapperError)) {
|
|
2038
|
+
throw new Error(`Wrapper-based OAuth import failed.
|
|
2039
|
+
${formatAuthRequiredMessage('autopilot', wrapperError, source)}`)
|
|
2040
|
+
}
|
|
2041
|
+
throw new Error(`MCP introspection failed via local wrapper: ${wrapperError instanceof Error ? wrapperError.message : String(wrapperError)}`)
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
1643
2044
|
}
|
|
1644
2045
|
|
|
1645
|
-
if (
|
|
1646
|
-
|
|
1647
|
-
{ value: 'bearer', label: 'bearer', hint: 'Authorization: Bearer <token>' },
|
|
1648
|
-
{ value: 'header', label: 'header', hint: 'Custom header such as X-API-Key' },
|
|
1649
|
-
], 'bearer')
|
|
2046
|
+
if (!initOptions.runtimeAuth && (discoveredAuth?.kind === 'platform' || usedOauthWrapper)) {
|
|
2047
|
+
runtimeAuthMode = 'platform'
|
|
1650
2048
|
}
|
|
1651
2049
|
|
|
1652
|
-
if (
|
|
1653
|
-
|
|
1654
|
-
|
|
2050
|
+
if (usedOauthWrapper) {
|
|
2051
|
+
authEnv = authEnv ?? (interactive
|
|
2052
|
+
? await clackText('Auth env var for generated non-platform targets (optional)', defaultAuthEnvVar(provider, discoveredAuth))
|
|
2053
|
+
: undefined)
|
|
2054
|
+
source = {
|
|
2055
|
+
...source,
|
|
2056
|
+
auth: authEnv
|
|
2057
|
+
? buildRemoteAuthConfig({
|
|
2058
|
+
authEnv,
|
|
2059
|
+
authType: provider?.tokenAuthType ?? 'bearer',
|
|
2060
|
+
authHeader: provider?.authHeader,
|
|
2061
|
+
authTemplate: provider?.authTemplate,
|
|
2062
|
+
})
|
|
2063
|
+
: { type: 'platform', mode: 'oauth' },
|
|
1655
2064
|
}
|
|
1656
|
-
|
|
1657
|
-
|
|
2065
|
+
} else {
|
|
2066
|
+
authEnv = authEnv ?? (interactive
|
|
2067
|
+
? await clackText('Auth env var for this MCP server', defaultAuthEnvVar(provider, discoveredAuth))
|
|
2068
|
+
: '')
|
|
2069
|
+
if (!authEnv) {
|
|
2070
|
+
throw new Error(formatAuthRequiredMessage('autopilot', error, source))
|
|
1658
2071
|
}
|
|
1659
|
-
}
|
|
1660
2072
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
2073
|
+
if (interactive && discoveredAuth?.kind === 'platform') {
|
|
2074
|
+
const credential = await maybeCaptureOAuthCredential(provider, discoveredAuth.authorizationUrl)
|
|
2075
|
+
applySessionCredential(authEnv, credential)
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
if (interactive && !authType) {
|
|
2079
|
+
authType = await clackSelect<'bearer' | 'header'>('Auth type', [
|
|
2080
|
+
{ value: 'bearer', label: 'bearer', hint: 'Authorization: Bearer <token>' },
|
|
2081
|
+
{ value: 'header', label: 'header', hint: 'Custom header such as X-API-Key' },
|
|
2082
|
+
], discoveredAuth?.kind === 'header' ? 'header' : (provider?.tokenAuthType ?? 'bearer'))
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
if (resolveRemoteAuthType({ authType, authHeader }) === 'header') {
|
|
2086
|
+
if (interactive && !authHeader) {
|
|
2087
|
+
authHeader = await clackText('Auth header name', discoveredAuth?.headerName ?? provider?.authHeader ?? 'X-API-Key')
|
|
2088
|
+
}
|
|
2089
|
+
if (interactive && !authTemplate) {
|
|
2090
|
+
authTemplate = await clackText('Auth header template', provider?.authTemplate ?? '${value}')
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
source = {
|
|
2095
|
+
...source,
|
|
2096
|
+
auth: buildRemoteAuthConfig({
|
|
2097
|
+
authEnv,
|
|
2098
|
+
authType,
|
|
2099
|
+
authHeader,
|
|
2100
|
+
authTemplate,
|
|
2101
|
+
}),
|
|
2102
|
+
}
|
|
2103
|
+
introspectionSource = source
|
|
2104
|
+
connectSpinner?.start('Autopilot · Reconnecting with auth...')
|
|
2105
|
+
try {
|
|
2106
|
+
introspection = await introspectMcpServer(introspectionSource)
|
|
2107
|
+
} catch (retryError) {
|
|
2108
|
+
if (isAuthRequiredError(retryError)) {
|
|
2109
|
+
throw new Error(`Authentication failed after retry.
|
|
2110
|
+
${formatAuthRequiredMessage('autopilot', retryError, source)}`)
|
|
2111
|
+
}
|
|
2112
|
+
throw new Error(`MCP introspection failed after auth retry: ${retryError instanceof Error ? retryError.message : String(retryError)}`)
|
|
1677
2113
|
}
|
|
1678
|
-
throw new Error(`MCP introspection failed after auth retry: ${retryError instanceof Error ? retryError.message : String(retryError)}`)
|
|
1679
2114
|
}
|
|
1680
2115
|
} else {
|
|
1681
2116
|
connectSpinner?.stop('Connection failed')
|
|
@@ -1683,6 +2118,10 @@ ${formatAuthRequiredMessage('autopilot', retryError)}`)
|
|
|
1683
2118
|
}
|
|
1684
2119
|
}
|
|
1685
2120
|
|
|
2121
|
+
if (!introspection) {
|
|
2122
|
+
throw new Error('MCP introspection did not return server metadata.')
|
|
2123
|
+
}
|
|
2124
|
+
|
|
1686
2125
|
const stdioHasEnv = source.transport === 'stdio'
|
|
1687
2126
|
&& source.env
|
|
1688
2127
|
&& Object.keys(source.env).length > 0
|
|
@@ -1703,12 +2142,13 @@ ${formatAuthRequiredMessage('autopilot', retryError)}`)
|
|
|
1703
2142
|
&& source.auth.type !== 'none'
|
|
1704
2143
|
&& !initOptions.runtimeAuth
|
|
1705
2144
|
) {
|
|
2145
|
+
runtimeAuthMode = source.auth.type === 'platform' ? 'platform' : runtimeAuthMode
|
|
1706
2146
|
runtimeAuthMode = await clackSelect<McpRuntimeAuthMode>('Claude/Cursor runtime auth', [
|
|
1707
2147
|
{ value: 'inline', label: 'inline', hint: 'Generate env/header auth directly into plugin output' },
|
|
1708
2148
|
{ value: 'platform', label: 'platform', hint: 'Use native platform-managed auth (for example OAuth/custom connector flows)' },
|
|
1709
2149
|
], runtimeAuthMode)
|
|
1710
2150
|
}
|
|
1711
|
-
connectSpinner?.stop(`Connected: ${introspection.serverInfo.title ?? introspection.serverInfo.name} (${introspection
|
|
2151
|
+
connectSpinner?.stop(`Connected: ${introspection.serverInfo.title ?? introspection.serverInfo.name} (${formatMcpDiscoverySummary(introspection)})`)
|
|
1712
2152
|
const quality = analyzeMcpQuality(introspection.tools)
|
|
1713
2153
|
|
|
1714
2154
|
if (!runtime.jsonOutput && !runtime.quiet && quality.issues.length > 0) {
|
|
@@ -1721,7 +2161,7 @@ ${formatAuthRequiredMessage('autopilot', retryError)}`)
|
|
|
1721
2161
|
? await clackText('Plugin name', defaultPluginName)
|
|
1722
2162
|
: defaultPluginName),
|
|
1723
2163
|
)
|
|
1724
|
-
const defaultDisplayName = initOptions.displayName ?? introspection
|
|
2164
|
+
const defaultDisplayName = initOptions.displayName ?? deriveDisplayName(introspection, pluginName)
|
|
1725
2165
|
const displayName = initOptions.displayName ?? (interactive
|
|
1726
2166
|
? await clackText('Display name', defaultDisplayName)
|
|
1727
2167
|
: defaultDisplayName)
|
|
@@ -1859,6 +2299,10 @@ ${formatAuthRequiredMessage('autopilot', retryError)}`)
|
|
|
1859
2299
|
source: rawSource,
|
|
1860
2300
|
mode,
|
|
1861
2301
|
runner,
|
|
2302
|
+
model: taxonomyPlan?.model ?? instructionsPlan?.model ?? reviewPlan?.model ?? {
|
|
2303
|
+
source: 'unknown',
|
|
2304
|
+
display: 'local default (CLI-managed)',
|
|
2305
|
+
},
|
|
1862
2306
|
targets,
|
|
1863
2307
|
toolCount: introspection.tools.length,
|
|
1864
2308
|
grouping,
|
|
@@ -1911,6 +2355,7 @@ ${formatAuthRequiredMessage('autopilot', retryError)}`)
|
|
|
1911
2355
|
console.log(` Mode: ${mode}`)
|
|
1912
2356
|
console.log(` Import: ${introspection.tools.length} tools -> ${targets.join(', ')}`)
|
|
1913
2357
|
console.log(` Runner: ${runner}`)
|
|
2358
|
+
console.log(` Model: ${summary.model.display}`)
|
|
1914
2359
|
console.log(` Workload: ${summarizeAutopilotWorkload({
|
|
1915
2360
|
taxonomy: passDecisions.taxonomy,
|
|
1916
2361
|
instructions: passDecisions.instructions,
|
|
@@ -2056,6 +2501,10 @@ ${formatAuthRequiredMessage('autopilot', retryError)}`)
|
|
|
2056
2501
|
source: rawSource,
|
|
2057
2502
|
mode,
|
|
2058
2503
|
runner,
|
|
2504
|
+
model: taxonomyPlan?.model ?? instructionsPlan?.model ?? reviewPlan?.model ?? {
|
|
2505
|
+
source: 'unknown',
|
|
2506
|
+
display: 'local default (CLI-managed)',
|
|
2507
|
+
},
|
|
2059
2508
|
targets,
|
|
2060
2509
|
toolCount: introspection.tools.length,
|
|
2061
2510
|
grouping,
|
|
@@ -2117,6 +2566,7 @@ ${formatAuthRequiredMessage('autopilot', retryError)}`)
|
|
|
2117
2566
|
console.log(` Mode: ${mode}`)
|
|
2118
2567
|
console.log(` Import: ${introspection.tools.length} tools -> ${targets.join(', ')}`)
|
|
2119
2568
|
console.log(` Runner: ${runner}`)
|
|
2569
|
+
console.log(` Model: ${summary.model.display}`)
|
|
2120
2570
|
console.log(` Workload: ${summarizeAutopilotWorkload({
|
|
2121
2571
|
taxonomy: passDecisions.taxonomy,
|
|
2122
2572
|
instructions: passDecisions.instructions,
|
|
@@ -2170,14 +2620,31 @@ async function runTestCommand() {
|
|
|
2170
2620
|
rootDir: process.cwd(),
|
|
2171
2621
|
targets,
|
|
2172
2622
|
})
|
|
2623
|
+
const config = result.config.ok ? await loadConfig() : null
|
|
2624
|
+
const platforms = targets ?? config?.targets ?? []
|
|
2625
|
+
const install = result.ok && config
|
|
2626
|
+
? await maybeInstallBuiltOutputs(config, platforms)
|
|
2627
|
+
: undefined
|
|
2173
2628
|
|
|
2174
2629
|
if (runtime.jsonOutput) {
|
|
2175
|
-
printJson(
|
|
2630
|
+
printJson({
|
|
2631
|
+
...result,
|
|
2632
|
+
install,
|
|
2633
|
+
})
|
|
2176
2634
|
return
|
|
2177
2635
|
}
|
|
2178
2636
|
|
|
2179
2637
|
if (!runtime.quiet) {
|
|
2180
2638
|
printTestResult(result)
|
|
2639
|
+
if (install) {
|
|
2640
|
+
console.log('Installed for local testing:')
|
|
2641
|
+
for (const target of install.installTargets) {
|
|
2642
|
+
console.log(` ${target.platform} -> ${target.pluginDir}`)
|
|
2643
|
+
}
|
|
2644
|
+
for (const note of install.notes) {
|
|
2645
|
+
console.log(note)
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2181
2648
|
}
|
|
2182
2649
|
|
|
2183
2650
|
if (!result.ok) {
|
|
@@ -2185,6 +2652,25 @@ async function runTestCommand() {
|
|
|
2185
2652
|
}
|
|
2186
2653
|
}
|
|
2187
2654
|
|
|
2655
|
+
async function runEvalCommand() {
|
|
2656
|
+
const report = await runEvalSuite({
|
|
2657
|
+
rootDir: process.cwd(),
|
|
2658
|
+
})
|
|
2659
|
+
|
|
2660
|
+
if (runtime.jsonOutput) {
|
|
2661
|
+
printJson(report)
|
|
2662
|
+
return
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
if (!runtime.quiet) {
|
|
2666
|
+
printEvalReport(report)
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
if (!report.ok) {
|
|
2670
|
+
process.exit(1)
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2188
2674
|
async function runInstall() {
|
|
2189
2675
|
const trust = args.includes('--trust')
|
|
2190
2676
|
const targets = parseTargetFlagValues(args)
|
|
@@ -2201,6 +2687,7 @@ async function runInstall() {
|
|
|
2201
2687
|
dryRun: true,
|
|
2202
2688
|
pluginName: config.name,
|
|
2203
2689
|
platforms,
|
|
2690
|
+
notes: getInstallFollowupNotes(platforms),
|
|
2204
2691
|
trustRequired: hookCommands.length > 0,
|
|
2205
2692
|
userConfig: plannedUserConfig.map((entry) => ({
|
|
2206
2693
|
key: entry.field.key,
|
|
@@ -2234,6 +2721,9 @@ async function runInstall() {
|
|
|
2234
2721
|
if (listHookCommands(config.hooks).length > 0) {
|
|
2235
2722
|
console.log(' trust reminder: this plugin defines local hook commands; install requires review or --trust')
|
|
2236
2723
|
}
|
|
2724
|
+
for (const note of getInstallFollowupNotes(platforms)) {
|
|
2725
|
+
console.log(` note: ${note}`)
|
|
2726
|
+
}
|
|
2237
2727
|
}
|
|
2238
2728
|
return
|
|
2239
2729
|
}
|
|
@@ -2338,25 +2828,45 @@ async function runMigrate() {
|
|
|
2338
2828
|
await migrate(inputPath)
|
|
2339
2829
|
}
|
|
2340
2830
|
|
|
2831
|
+
async function runMcp() {
|
|
2832
|
+
const subcommand = args[1]
|
|
2833
|
+
if (subcommand !== 'proxy') {
|
|
2834
|
+
console.error('Usage: pluxx mcp proxy --from-mcp <source> [--record <tape.json>]')
|
|
2835
|
+
console.error(' pluxx mcp proxy --replay <tape.json>')
|
|
2836
|
+
process.exit(1)
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
try {
|
|
2840
|
+
await runMcpProxy(args.slice(2))
|
|
2841
|
+
} catch (error) {
|
|
2842
|
+
if (error instanceof Error && error.message === 'Invalid MCP proxy arguments.') {
|
|
2843
|
+
process.exit(1)
|
|
2844
|
+
}
|
|
2845
|
+
throw error
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2341
2849
|
function printHelp() {
|
|
2342
2850
|
console.log(`
|
|
2343
2851
|
pluxx — Cross-platform AI agent plugin SDK
|
|
2344
2852
|
|
|
2345
2853
|
Usage:
|
|
2346
|
-
pluxx build [--target <platforms...>] Generate platform-specific plugin files
|
|
2854
|
+
pluxx build [--target <platforms...>] [--install] Generate platform-specific plugin files
|
|
2347
2855
|
pluxx dev [--target <platforms...>] Watch for changes and auto-rebuild
|
|
2348
2856
|
pluxx validate Validate your config
|
|
2349
2857
|
pluxx lint Lint skills and cross-platform metadata
|
|
2350
|
-
pluxx doctor
|
|
2858
|
+
pluxx doctor [path] [--consumer] Check source-project or installed-bundle health
|
|
2351
2859
|
pluxx agent prepare Generate agent context + boundary files for host agents
|
|
2352
2860
|
pluxx agent prompt <kind> Generate a prompt pack (taxonomy, instructions, review)
|
|
2353
2861
|
pluxx agent run <kind> --runner <id> Execute a prompt pack via Claude, Cursor, Codex, or OpenCode headlessly
|
|
2862
|
+
pluxx mcp proxy ... Run a local MCP proxy with optional record/replay tapes
|
|
2354
2863
|
pluxx autopilot --from-mcp ... Run import + agent refinement + verification in one command
|
|
2355
2864
|
pluxx init [name] [--from-mcp <source>] Create a new pluxx.config.ts
|
|
2356
2865
|
pluxx sync [--from-mcp <source>] Refresh MCP-derived scaffold files
|
|
2357
2866
|
pluxx migrate <path> Import an existing plugin into pluxx
|
|
2358
|
-
pluxx test [--target <platforms...>] Run config, lint, build, and smoke checks
|
|
2359
|
-
pluxx
|
|
2867
|
+
pluxx test [--target <platforms...>] [--install] Run config, lint, eval, build, and smoke checks
|
|
2868
|
+
pluxx eval Evaluate scaffold and prompt-pack quality
|
|
2869
|
+
pluxx install [--target <platforms>] [--trust] Install built plugins for local testing
|
|
2360
2870
|
pluxx publish [--npm] [--github-release] [--dry-run] [--json] [--tag latest] [--version x.y.z]
|
|
2361
2871
|
pluxx uninstall [--target <platforms>] Remove symlinked plugins
|
|
2362
2872
|
pluxx help Show this help
|
|
@@ -2374,12 +2884,15 @@ Targets:
|
|
|
2374
2884
|
|
|
2375
2885
|
Examples:
|
|
2376
2886
|
pluxx build Build for all configured targets
|
|
2887
|
+
pluxx build --install Build and install all configured targets locally
|
|
2377
2888
|
pluxx build --target claude-code cursor Build for specific platforms
|
|
2378
2889
|
pluxx init my-plugin Scaffold a new plugin config
|
|
2379
2890
|
pluxx init --from-mcp https://example.com/mcp Scaffold from a remote MCP server
|
|
2380
2891
|
pluxx init --from-mcp "npx -y @acme/mcp" Scaffold from a local MCP command
|
|
2381
2892
|
pluxx init --from-mcp https://example.com/mcp --yes --name acme --display-name "Acme" --author "Acme" --targets claude-code,codex --grouping workflow --hooks safe --json
|
|
2382
2893
|
pluxx init --from-mcp https://example.com/mcp --yes --auth-env API_KEY --auth-type header --auth-header X-API-Key --auth-template "\${value}"
|
|
2894
|
+
pluxx init --from-mcp https://example.com/mcp --yes --auth-type platform --runtime-auth platform
|
|
2895
|
+
pluxx init --from-mcp https://mcp.linear.app/mcp --yes --oauth-wrapper --runtime-auth platform
|
|
2383
2896
|
pluxx init --from-mcp https://example.com/sse --transport sse Scaffold from an SSE-transport MCP server
|
|
2384
2897
|
pluxx init --from-mcp https://example.com/mcp --yes --dry-run Preview scaffold files without writing
|
|
2385
2898
|
pluxx sync Refresh a scaffold using .pluxx/mcp.json metadata
|
|
@@ -2392,13 +2905,19 @@ Examples:
|
|
|
2392
2905
|
pluxx agent run taxonomy --runner codex
|
|
2393
2906
|
pluxx agent run taxonomy --runner codex --verbose-runner
|
|
2394
2907
|
pluxx agent run review --runner opencode --attach http://localhost:4096 --no-verify
|
|
2908
|
+
pluxx mcp proxy --from-mcp "bun ./server.js" --record .pluxx/tapes/dev.json
|
|
2909
|
+
pluxx mcp proxy --replay .pluxx/tapes/dev.json
|
|
2395
2910
|
--attach is only supported for the opencode runner
|
|
2396
2911
|
pluxx autopilot --from-mcp https://example.com/mcp --runner codex --mode quick --yes
|
|
2397
2912
|
pluxx autopilot --from-mcp https://example.com/mcp --runner codex --mode standard --yes --name acme --display-name "Acme"
|
|
2398
2913
|
pluxx autopilot --from-mcp https://example.com/mcp --runner codex --mode thorough --yes --verbose-runner
|
|
2914
|
+
pluxx autopilot --from-mcp https://mcp.linear.app/mcp --runner codex --yes --oauth-wrapper
|
|
2399
2915
|
pluxx autopilot --from-mcp "npx -y @acme/mcp" --runner claude --targets claude-code,codex --website https://example.com --docs https://docs.example.com
|
|
2400
|
-
pluxx doctor --json Inspect project health as JSON
|
|
2916
|
+
pluxx doctor --json Inspect source-project health as JSON
|
|
2917
|
+
pluxx doctor --consumer ./dist/cursor Inspect a built or installed platform bundle
|
|
2918
|
+
pluxx eval --json Inspect scaffold/prompt-pack quality as JSON
|
|
2401
2919
|
pluxx test --target claude-code codex Verify selected target outputs
|
|
2920
|
+
pluxx test --install Verify and install all configured targets locally
|
|
2402
2921
|
pluxx install Install to all configured targets
|
|
2403
2922
|
pluxx install --target claude-code Install to Claude Code only
|
|
2404
2923
|
pluxx install --dry-run Preview local install paths and trust implications
|