Merge pull request 'fix: use jq for completion name extraction to avoid nested matches' (#30) from fix/completion-nested-names into main
This commit was merged in pull request #30.
This commit is contained in:
@@ -46,16 +46,16 @@ _mcpctl() {
|
|||||||
# If completing the --project value
|
# If completing the --project value
|
||||||
if [[ "$prev" == "--project" ]]; then
|
if [[ "$prev" == "--project" ]]; then
|
||||||
local names
|
local names
|
||||||
names=$(mcpctl get projects -o json 2>/dev/null | grep -oP '"name":\s*"\K[^"]+')
|
names=$(mcpctl get projects -o json 2>/dev/null | jq -r '.[].name' 2>/dev/null)
|
||||||
COMPREPLY=($(compgen -W "$names" -- "$cur"))
|
COMPREPLY=($(compgen -W "$names" -- "$cur"))
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fetch resource names dynamically
|
# Fetch resource names dynamically (jq extracts only top-level names)
|
||||||
_mcpctl_resource_names() {
|
_mcpctl_resource_names() {
|
||||||
local rt="$1"
|
local rt="$1"
|
||||||
if [[ -n "$rt" ]]; then
|
if [[ -n "$rt" ]]; then
|
||||||
mcpctl get "$rt" -o json 2>/dev/null | grep -oP '"name":\s*"\K[^"]+'
|
mcpctl get "$rt" -o json 2>/dev/null | jq -r '.[].name' 2>/dev/null
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,19 +66,18 @@ function __mcpctl_get_resource_type
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Fetch resource names dynamically from the API
|
# Fetch resource names dynamically from the API (jq extracts only top-level names)
|
||||||
function __mcpctl_resource_names
|
function __mcpctl_resource_names
|
||||||
set -l resource (__mcpctl_get_resource_type)
|
set -l resource (__mcpctl_get_resource_type)
|
||||||
if test -z "$resource"
|
if test -z "$resource"
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
# Use mcpctl to fetch names (quick JSON parse with string manipulation)
|
mcpctl get $resource -o json 2>/dev/null | jq -r '.[].name' 2>/dev/null
|
||||||
mcpctl get $resource -o json 2>/dev/null | string match -rg '"name":\s*"([^"]+)"'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Fetch project names for --project value
|
# Fetch project names for --project value
|
||||||
function __mcpctl_project_names
|
function __mcpctl_project_names
|
||||||
mcpctl get projects -o json 2>/dev/null | string match -rg '"name":\s*"([^"]+)"'
|
mcpctl get projects -o json 2>/dev/null | jq -r '.[].name' 2>/dev/null
|
||||||
end
|
end
|
||||||
|
|
||||||
# --project value completion
|
# --project value completion
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ release: "1"
|
|||||||
maintainer: michal
|
maintainer: michal
|
||||||
description: kubectl-like CLI for managing MCP servers
|
description: kubectl-like CLI for managing MCP servers
|
||||||
license: MIT
|
license: MIT
|
||||||
|
depends:
|
||||||
|
- jq
|
||||||
contents:
|
contents:
|
||||||
- src: ./dist/mcpctl
|
- src: ./dist/mcpctl
|
||||||
dst: /usr/bin/mcpctl
|
dst: /usr/bin/mcpctl
|
||||||
|
|||||||
48
pr.sh
Executable file
48
pr.sh
Executable file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Usage: source .env && bash pr.sh "PR title" "PR body"
|
||||||
|
# Requires GITEA_TOKEN in environment
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
GITEA_URL="http://10.0.0.194:3012"
|
||||||
|
REPO="michal/mcpctl"
|
||||||
|
|
||||||
|
TITLE="${1:?Usage: pr.sh <title> [body]}"
|
||||||
|
BODY="${2:-}"
|
||||||
|
BASE="${3:-main}"
|
||||||
|
HEAD=$(git rev-parse --abbrev-ref HEAD)
|
||||||
|
|
||||||
|
if [ "$HEAD" = "$BASE" ]; then
|
||||||
|
echo "Error: already on $BASE, switch to a feature branch first" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||||
|
echo "Error: GITEA_TOKEN not set. Run: source .env" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Push if needed
|
||||||
|
if ! git rev-parse --verify "origin/$HEAD" &>/dev/null; then
|
||||||
|
git push -u origin "$HEAD"
|
||||||
|
else
|
||||||
|
git push
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create PR
|
||||||
|
RESPONSE=$(curl -s -X POST "$GITEA_URL/api/v1/repos/$REPO/pulls" \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$(jq -n --arg t "$TITLE" --arg b "$BODY" --arg h "$HEAD" --arg base "$BASE" \
|
||||||
|
'{title: $t, body: $b, head: $h, base: $base}')")
|
||||||
|
|
||||||
|
PR_NUM=$(echo "$RESPONSE" | jq -r '.number // empty')
|
||||||
|
PR_URL=$(echo "$RESPONSE" | jq -r '.html_url // empty')
|
||||||
|
|
||||||
|
if [ -z "$PR_NUM" ]; then
|
||||||
|
echo "Error creating PR:" >&2
|
||||||
|
echo "$RESPONSE" | jq . 2>/dev/null || echo "$RESPONSE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "PR #$PR_NUM: https://mysources.co.uk/$REPO/pulls/$PR_NUM"
|
||||||
@@ -72,6 +72,19 @@ describe('fish completions', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('resource name functions use jq (not regex) to avoid matching nested name fields', () => {
|
||||||
|
// Regex like "name":\s*"..." on JSON matches nested server names inside project objects.
|
||||||
|
// Must use jq -r '.[].name' to extract only top-level names.
|
||||||
|
const resourceNamesFn = fishFile.match(/function __mcpctl_resource_names[\s\S]*?^end/m)?.[0] ?? '';
|
||||||
|
const projectNamesFn = fishFile.match(/function __mcpctl_project_names[\s\S]*?^end/m)?.[0] ?? '';
|
||||||
|
|
||||||
|
expect(resourceNamesFn, '__mcpctl_resource_names must use jq').toContain("jq -r '.[].name'");
|
||||||
|
expect(resourceNamesFn, '__mcpctl_resource_names must not use string match on name').not.toMatch(/string match.*"name"/);
|
||||||
|
|
||||||
|
expect(projectNamesFn, '__mcpctl_project_names must use jq').toContain("jq -r '.[].name'");
|
||||||
|
expect(projectNamesFn, '__mcpctl_project_names must not use string match on name').not.toMatch(/string match.*"name"/);
|
||||||
|
});
|
||||||
|
|
||||||
it('non-project commands do not show with --project', () => {
|
it('non-project commands do not show with --project', () => {
|
||||||
const nonProjectCmds = ['status', 'login', 'logout', 'config', 'apply', 'backup', 'restore'];
|
const nonProjectCmds = ['status', 'login', 'logout', 'config', 'apply', 'backup', 'restore'];
|
||||||
const lines = fishFile.split('\n').filter((l) => l.startsWith('complete') && l.includes('-a '));
|
const lines = fishFile.split('\n').filter((l) => l.startsWith('complete') && l.includes('-a '));
|
||||||
@@ -112,4 +125,10 @@ describe('bash completions', () => {
|
|||||||
it('defines --project option', () => {
|
it('defines --project option', () => {
|
||||||
expect(bashFile).toContain('--project');
|
expect(bashFile).toContain('--project');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('resource name function uses jq (not grep regex) to avoid matching nested name fields', () => {
|
||||||
|
const fnMatch = bashFile.match(/_mcpctl_resource_names\(\)[\s\S]*?\n\s*\}/)?.[0] ?? '';
|
||||||
|
expect(fnMatch, '_mcpctl_resource_names must use jq').toContain("jq -r '.[].name'");
|
||||||
|
expect(fnMatch, '_mcpctl_resource_names must not use grep on name').not.toMatch(/grep.*"name"/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user