feat(extensions): manage MCPorter CLI

This commit is contained in:
fraujulian committed 2026-09-18 11:36:39 +02:00
1 parent 11c2b6dfcb
commit c37ffe4c66
4 files changed
+28 -31

No files matched your search

+1
View File
@@ -5,6 +5,7 @@ Superpowers - superpowers@claude-plugins-official openai-curated-remote superpow
Context7 upstash/context7 context7@context7-marketplace upstash/context7 context7@context7-marketplace plugin - - Context7 upstash/context7 context7@context7-marketplace upstash/context7 context7@context7-marketplace plugin - -
Caveman JuliusBrussee/caveman caveman@caveman thinkhome-org/caveman-codex caveman@thinkhome-caveman plugin - - Caveman JuliusBrussee/caveman caveman@caveman thinkhome-org/caveman-codex caveman@thinkhome-caveman plugin - -
QMD tobi/qmd qmd@qmd - - qmd @tobilu/qmd qmd QMD tobi/qmd qmd@qmd - - qmd @tobilu/qmd qmd
MCPorter - mcporter - mcporter cli mcporter -
Humanizer blader/humanizer humanizer@humanizer - - skill blader/humanizer humanizer Humanizer blader/humanizer humanizer@humanizer - - skill blader/humanizer humanizer
Impeccable pbakaus/impeccable impeccable@impeccable pbakaus/impeccable impeccable skill pbakaus/impeccable impeccable Impeccable pbakaus/impeccable impeccable@impeccable pbakaus/impeccable impeccable skill pbakaus/impeccable impeccable
Anthropic Frontend Design anthropics/claude-plugins-official frontend-design@claude-plugins-official - - skill anthropics/skills frontend-design Anthropic Frontend Design anthropics/claude-plugins-official frontend-design@claude-plugins-official - - skill anthropics/skills frontend-design
1 name claude_marketplace claude_plugin codex_marketplace codex_plugin codex_method codex_source codex_skill
5 Context7 upstash/context7 context7@context7-marketplace upstash/context7 context7@context7-marketplace plugin - -
6 Caveman JuliusBrussee/caveman caveman@caveman thinkhome-org/caveman-codex caveman@thinkhome-caveman plugin - -
7 QMD tobi/qmd qmd@qmd - - qmd @tobilu/qmd qmd
8 MCPorter - mcporter - mcporter cli mcporter -
9 Humanizer blader/humanizer humanizer@humanizer - - skill blader/humanizer humanizer
10 Impeccable pbakaus/impeccable impeccable@impeccable pbakaus/impeccable impeccable skill pbakaus/impeccable impeccable
11 Anthropic Frontend Design anthropics/claude-plugins-official frontend-design@claude-plugins-official - - skill anthropics/skills frontend-design
+22 -31
View File
@@ -166,7 +166,7 @@ class Manager:
raise ValueError('Invalid extension ownership ledger.') raise ValueError('Invalid extension ownership ledger.')
identities = set() identities = set()
for resource in self.state['resources']: for resource in self.state['resources']:
if not isinstance(resource, dict) or resource.get('client') not in ('codex', 'claude') or resource.get('kind') not in ('plugin', 'skill', 'qmd'): if not isinstance(resource, dict) or resource.get('client') not in ('codex', 'claude') or resource.get('kind') not in ('plugin', 'skill', 'qmd', 'cli'):
raise ValueError('Invalid extension ownership record.') raise ValueError('Invalid extension ownership record.')
if not isinstance(resource.get('name'), str) or not resource['name']: if not isinstance(resource.get('name'), str) or not resource['name']:
raise ValueError('Invalid extension ownership name.') raise ValueError('Invalid extension ownership name.')
@@ -192,8 +192,10 @@ class Manager:
if not relative.startswith(directory + '/') or not re.fullmatch(r'[a-f0-9]{64}', value): if not relative.startswith(directory + '/') or not re.fullmatch(r'[a-f0-9]{64}', value):
raise ValueError('Invalid owned skill file record.') raise ValueError('Invalid owned skill file record.')
safe_path(self.home, relative) safe_path(self.home, relative)
elif resource.get('package') != '@tobilu/qmd' or resource['client'] != 'codex': elif resource['kind'] == 'qmd' and (resource.get('package') != '@tobilu/qmd' or resource['client'] != 'codex'):
raise ValueError('Invalid owned QMD record.') raise ValueError('Invalid owned QMD record.')
elif resource['kind'] == 'cli' and not re.fullmatch(r'[a-z0-9@/_.-]+', resource.get('package', '')):
raise ValueError('Invalid owned CLI record.')
def save(self): def save(self):
if not self.dry_run: if not self.dry_run:
@@ -227,41 +229,19 @@ class Manager:
self.command(arguments) self.command(arguments)
self.cache[client].pop(selector, None) self.cache[client].pop(selector, None)
elif record['kind'] == 'skill': elif record['kind'] == 'skill':
empty_candidates = set()
for relative, expected in list(record['files'].items()):
path = safe_path(self.home, relative)
if path.exists():
if not path.is_file() or digest(path) != expected:
print(f'WARN Preserving modified skill file: {relative}')
continue
path.unlink()
parent = path.parent
directory = safe_path(self.home, record['directory'])
while parent != directory.parent:
empty_candidates.add(parent)
parent = parent.parent
del record['files'][relative]
self.save()
directory = safe_path(self.home, record['directory']) directory = safe_path(self.home, record['directory'])
if directory.exists(): if directory.exists():
for path in sorted(empty_candidates, key=lambda p: len(p.parts), reverse=True): shutil.rmtree(directory)
if path.is_dir() and not path.is_symlink():
try:
path.rmdir()
except OSError:
pass
try:
directory.rmdir()
except OSError:
pass
if record['files']:
return
else: else:
self.command([record['client'], 'mcp', 'remove', 'qmd'] + (['--scope', 'user'] if record['client'] == 'claude' else [])) package = record['package']
self.command(['npm', 'uninstall', '--global', package])
self.state['resources'].remove(record) self.state['resources'].remove(record)
self.save() self.save()
def ensure(self, client, entry): def ensure(self, client, entry):
if entry['codex_method'] == 'cli':
self.ensure_cli(client, entry)
return
if entry['codex_method'] == 'qmd': if entry['codex_method'] == 'qmd':
self.ensure_qmd(client, entry) self.ensure_qmd(client, entry)
return return
@@ -326,6 +306,18 @@ class Manager:
self.save() self.save()
self.cache[client][selector] = {'scope': 'user'} self.cache[client][selector] = {'scope': 'user'}
def ensure_cli(self, client, entry):
package = entry['codex_source']
if not re.fullmatch(r'[a-z0-9@/_.-]+', package):
raise ValueError('Invalid CLI package.')
record = next((r for r in self.state['resources'] if r['client'] == client and r['name'] == entry['name']), None)
if record is not None:
return
self.command(['npm', 'install', '--global', package])
if not self.dry_run:
self.state['resources'].append(dict(client=client, name=entry['name'], kind='cli', package=package))
self.save()
def ensure_qmd(self, client, entry): def ensure_qmd(self, client, entry):
package = entry['codex_source'] package = entry['codex_source']
if package != '@tobilu/qmd': if package != '@tobilu/qmd':
@@ -340,7 +332,6 @@ class Manager:
record = next((r for r in self.state['resources'] if r['client'] == client and r['name'] == entry['name']), None) record = next((r for r in self.state['resources'] if r['client'] == client and r['name'] == entry['name']), None)
if record is not None: if record is not None:
return return
self.command(['codex', 'mcp', 'add', 'qmd', '--', 'qmd', 'mcp'])
self.state['resources'].append(dict(client='codex', name=entry['name'], kind='qmd', package=package)) self.state['resources'].append(dict(client='codex', name=entry['name'], kind='qmd', package=package))
self.save() self.save()
+1
View File
@@ -96,6 +96,7 @@ function Select-ConfiguredPlugins {
$idField = if ($referenceClient -eq 'Claude') { 'id' } else { 'pluginId' } $idField = if ($referenceClient -eq 'Claude') { 'id' } else { 'pluginId' }
$checked = @($toggleable | ForEach-Object { $checked = @($toggleable | ForEach-Object {
if ($Mode -eq 'Install') { $true; return } if ($Mode -eq 'Install') { $true; return }
if ($_.codex_method -eq 'cli') { return $null -ne (Get-Command $_.codex_source -ErrorAction SilentlyContinue) }
$selector = if ($referenceClient -eq 'Claude') { $_.claude_plugin } else { $_.codex_plugin } $selector = if ($referenceClient -eq 'Claude') { $_.claude_plugin } else { $_.codex_plugin }
if ($referenceClient -eq 'Codex' -and $_.codex_method -eq 'qmd') { if ($referenceClient -eq 'Codex' -and $_.codex_method -eq 'qmd') {
$null -ne (Get-Command qmd -ErrorAction SilentlyContinue) $null -ne (Get-Command qmd -ErrorAction SilentlyContinue)
+4
View File
@@ -110,6 +110,10 @@ select_configured_plugins() {
checked+=(true) checked+=(true)
continue continue
fi fi
if [ "$(plugin_field "$root" "$name" codex_method)" = cli ]; then
if command -v "$(plugin_field "$root" "$name" codex_source)" >/dev/null 2>&1; then checked+=(true); else checked+=(false); fi
continue
fi
if [ "$reference_client" = claude ]; then selector=$(plugin_field "$root" "$name" claude_plugin); else selector=$(plugin_field "$root" "$name" codex_plugin); fi if [ "$reference_client" = claude ]; then selector=$(plugin_field "$root" "$name" claude_plugin); else selector=$(plugin_field "$root" "$name" codex_plugin); fi
if [ "$reference_client" = codex ] && [ "$(plugin_field "$root" "$name" codex_method)" = qmd ]; then if [ "$reference_client" = codex ] && [ "$(plugin_field "$root" "$name" codex_method)" = qmd ]; then
if command -v qmd >/dev/null 2>&1; then checked+=(true); else checked+=(false); fi if command -v qmd >/dev/null 2>&1; then checked+=(true); else checked+=(false); fi