Skip to content

Commit 1875fa3

Browse files
committed
feat: add install script, CI pipeline, and bump to v0.5.0
- Add curl-pipeable install.sh that handles fresh install and updates - Add CI pipeline with manifest validation and Docker-based installer tests - Fix version mismatch between marketplace (0.3.0) and plugin (0.4.0) - Bump both to 0.5.0
1 parent 88e111a commit 1875fa3

File tree

9 files changed

+266
-2
lines changed

9 files changed

+266
-2
lines changed

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
},
66
"metadata": {
77
"description": "A collection of Claude Code skills for working with Exasol databases",
8-
"version": "0.3.0"
8+
"version": "0.5.0"
99
},
1010
"plugins": [
1111
{

.github/workflows/ci.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: CI
2+
on:
3+
push:
4+
branches: [main]
5+
pull_request:
6+
branches: [main]
7+
8+
jobs:
9+
validate-plugin:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- name: Validate marketplace manifest
14+
run: |
15+
jq empty .claude-plugin/marketplace.json
16+
jq -e '.name and .owner.name and .metadata.version and .plugins' .claude-plugin/marketplace.json
17+
- name: Validate plugin manifest
18+
run: |
19+
jq empty plugins/exasol/.claude-plugin/plugin.json
20+
jq -e '.name and .version and .author.name' plugins/exasol/.claude-plugin/plugin.json
21+
- name: Check version consistency
22+
run: |
23+
mp=$(jq -r '.metadata.version' .claude-plugin/marketplace.json)
24+
pl=$(jq -r '.version' plugins/exasol/.claude-plugin/plugin.json)
25+
echo "Marketplace: $mp | Plugin: $pl"
26+
[ "$mp" = "$pl" ] || { echo "::error::Version mismatch: marketplace=$mp plugin=$pl"; exit 1; }
27+
28+
test-installer:
29+
runs-on: ubuntu-latest
30+
steps:
31+
- uses: actions/checkout@v4
32+
- name: Build test image
33+
run: docker build -f Dockerfile.test -t installer-test .
34+
- name: "Test: fresh install"
35+
run: docker run --rm -e SCENARIO=fresh installer-test sh test/test-installer.sh
36+
- name: "Test: idempotent re-run"
37+
run: docker run --rm -e SCENARIO=idempotent installer-test sh test/test-installer.sh
38+
- name: "Test: update from older version"
39+
run: docker run --rm -e SCENARIO=update installer-test sh test/test-installer.sh

CLAUDE.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ exasol-skills/
2020
│ │ └── exasol-sql.md
2121
│ └── commands/
2222
│ └── exasol.md # /exasol slash command
23+
├── install.sh # Curl-pipeable installer (idempotent)
24+
├── Dockerfile.test # Docker image for installer CI tests
25+
├── test/
26+
│ ├── mock-claude.sh # Mock claude CLI for testing
27+
│ └── test-installer.sh # Test runner (fresh/idempotent/update)
28+
├── .github/workflows/ci.yml # CI: validate manifests + test installer
2329
├── CLAUDE.md # This file
2430
├── README.md
2531
└── LICENSE
@@ -40,6 +46,16 @@ exasol-skills/
4046
3. Add commands in `plugins/<name>/commands/<command>.md`
4147
4. Register the plugin in `.claude-plugin/marketplace.json`
4248

49+
## Installation
50+
51+
End users install via the one-liner:
52+
53+
```bash
54+
curl -fsSL https://raw.githubusercontent.com/exasol-labs/exasol-agent-skills/main/install.sh | sh
55+
```
56+
57+
`install.sh` is idempotent: it adds the marketplace and installs the plugin on first run, and updates both on subsequent runs. It requires only the `claude` CLI and POSIX tools (no `jq`).
58+
4359
## Local Development
4460

4561
To test this marketplace locally:

Dockerfile.test

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM alpine:3.20
2+
RUN apk add --no-cache bash grep sed coreutils
3+
WORKDIR /workspace
4+
COPY install.sh .
5+
COPY test/ test/
6+
RUN chmod +x install.sh test/*.sh

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,24 @@ Claude Code plugin marketplace for [Exasol](https://exasol.com) — gives Claude
1616

1717
## Get Started
1818

19+
**One-line install:**
20+
21+
```bash
22+
curl -fsSL https://raw.githubusercontent.com/exasol-labs/exasol-agent-skills/main/install.sh | sh
23+
```
24+
25+
Running this again updates to the latest version.
26+
27+
<details>
28+
<summary>Manual install</summary>
29+
1930
```bash
2031
claude plugin marketplace add exasol-labs/exasol-agent-skills
2132
claude plugin install exasol@exasol-skills
2233
```
2334

35+
</details>
36+
2437
That's it. The skill and `/exasol` slash command are now available in your Claude Code sessions.
2538

2639
---

install.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/bin/sh
2+
set -e
3+
4+
MARKETPLACE_NAME="exasol-skills"
5+
MARKETPLACE_REPO="exasol-labs/exasol-agent-skills"
6+
PLUGIN_ID="exasol@${MARKETPLACE_NAME}"
7+
PLUGIN_NAME="exasol"
8+
9+
info() { printf '\033[0;34m[info]\033[0m %s\n' "$1"; }
10+
ok() { printf '\033[0;32m[ok]\033[0m %s\n' "$1"; }
11+
warn() { printf '\033[0;33m[warn]\033[0m %s\n' "$1"; }
12+
fail() { printf '\033[0;31m[error]\033[0m %s\n' "$1" >&2; exit 1; }
13+
14+
command -v claude >/dev/null 2>&1 || fail "claude CLI not found. Install: https://docs.anthropic.com/en/docs/claude-code/overview"
15+
16+
# --- marketplace ---
17+
info "Checking marketplace..."
18+
if claude plugin marketplace list --json 2>/dev/null | grep -q "\"${MARKETPLACE_NAME}\""; then
19+
info "Marketplace '${MARKETPLACE_NAME}' found. Updating..."
20+
claude plugin marketplace update "${MARKETPLACE_NAME}" 2>/dev/null || true
21+
else
22+
info "Adding marketplace '${MARKETPLACE_NAME}'..."
23+
claude plugin marketplace add "${MARKETPLACE_REPO}"
24+
fi
25+
26+
# --- plugin ---
27+
info "Checking plugin..."
28+
if claude plugin list --json 2>/dev/null | grep -q "\"${PLUGIN_ID}\""; then
29+
info "Plugin '${PLUGIN_ID}' found. Updating..."
30+
claude plugin update "${PLUGIN_NAME}" --scope user 2>/dev/null || true
31+
else
32+
info "Installing plugin '${PLUGIN_ID}'..."
33+
claude plugin install "${PLUGIN_ID}" --scope user
34+
fi
35+
36+
# --- verify ---
37+
info "Verifying..."
38+
installed_version="$(claude plugin list --json 2>/dev/null | grep -A5 "\"${PLUGIN_ID}\"" | grep '"version"' | head -1 | sed 's/.*"version"[^"]*"\([^"]*\)".*/\1/')"
39+
40+
if [ -n "$installed_version" ]; then
41+
ok "Exasol plugin v${installed_version} installed. Start a new Claude Code session to use it."
42+
else
43+
warn "Could not verify version, but installation may have succeeded."
44+
ok "Run 'claude plugin list' to check. Start a new Claude Code session to use it."
45+
fi

plugins/exasol/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "exasol",
33
"description": "Interact with Exasol databases using the exapump CLI tool. Upload CSV/Parquet files, run SQL queries, export data, and leverage Exasol-specific SQL features.",
4-
"version": "0.4.0",
4+
"version": "0.5.0",
55
"author": {
66
"name": "exasol-labs"
77
}

test/mock-claude.sh

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/bin/sh
2+
# Mock claude CLI for testing install.sh
3+
# Uses STATE_DIR env var to track marketplace/plugin state.
4+
5+
STATE_DIR="${STATE_DIR:-/tmp/mock-claude-state}"
6+
mkdir -p "$STATE_DIR"
7+
8+
MARKETPLACE_FILE="$STATE_DIR/marketplace"
9+
PLUGIN_FILE="$STATE_DIR/plugin"
10+
PLUGIN_VERSION_FILE="$STATE_DIR/plugin_version"
11+
12+
case "$*" in
13+
"plugin marketplace list --json")
14+
if [ -f "$MARKETPLACE_FILE" ]; then
15+
cat <<EOF
16+
[{"name": "exasol-skills", "source": "exasol-labs/exasol-agent-skills"}]
17+
EOF
18+
else
19+
echo "[]"
20+
fi
21+
;;
22+
23+
"plugin marketplace add "*)
24+
touch "$MARKETPLACE_FILE"
25+
echo "Marketplace added."
26+
;;
27+
28+
"plugin marketplace update "*)
29+
if [ -f "$MARKETPLACE_FILE" ]; then
30+
echo "Marketplace updated."
31+
else
32+
echo "Marketplace not found." >&2
33+
exit 1
34+
fi
35+
;;
36+
37+
"plugin list --json")
38+
if [ -f "$PLUGIN_FILE" ]; then
39+
version="$(cat "$PLUGIN_VERSION_FILE" 2>/dev/null || echo "0.5.0")"
40+
cat <<EOF
41+
[{"name": "exasol@exasol-skills", "version": "$version"}]
42+
EOF
43+
else
44+
echo "[]"
45+
fi
46+
;;
47+
48+
"plugin install "*)
49+
touch "$PLUGIN_FILE"
50+
echo "0.5.0" > "$PLUGIN_VERSION_FILE"
51+
echo "Plugin installed."
52+
;;
53+
54+
"plugin update "*)
55+
if [ -f "$PLUGIN_FILE" ]; then
56+
echo "0.5.0" > "$PLUGIN_VERSION_FILE"
57+
echo "Plugin updated."
58+
else
59+
echo "Plugin not found." >&2
60+
exit 1
61+
fi
62+
;;
63+
64+
*)
65+
echo "Unknown command: $*" >&2
66+
exit 1
67+
;;
68+
esac

test/test-installer.sh

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/bin/sh
2+
set -e
3+
4+
SCENARIO="${SCENARIO:-fresh}"
5+
STATE_DIR="/tmp/mock-claude-state"
6+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7+
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
8+
9+
pass() { printf '\033[0;32mPASS\033[0m %s\n' "$1"; }
10+
fail() { printf '\033[0;31mFAIL\033[0m %s\n' "$1" >&2; exit 1; }
11+
12+
# Clean state
13+
rm -rf "$STATE_DIR"
14+
mkdir -p "$STATE_DIR"
15+
16+
# Prepend mock claude to PATH
17+
export PATH="$SCRIPT_DIR:$PATH"
18+
export STATE_DIR
19+
20+
# Create a "claude" wrapper that calls mock-claude.sh
21+
cat > "$SCRIPT_DIR/claude" <<'WRAPPER'
22+
#!/bin/sh
23+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
24+
exec sh "$SCRIPT_DIR/mock-claude.sh" "$@"
25+
WRAPPER
26+
chmod +x "$SCRIPT_DIR/claude"
27+
28+
# Set up scenario
29+
case "$SCENARIO" in
30+
fresh)
31+
echo "=== Scenario: fresh install ==="
32+
;;
33+
idempotent)
34+
echo "=== Scenario: idempotent re-run ==="
35+
touch "$STATE_DIR/marketplace"
36+
touch "$STATE_DIR/plugin"
37+
echo "0.5.0" > "$STATE_DIR/plugin_version"
38+
;;
39+
update)
40+
echo "=== Scenario: update from older version ==="
41+
touch "$STATE_DIR/marketplace"
42+
touch "$STATE_DIR/plugin"
43+
echo "0.3.0" > "$STATE_DIR/plugin_version"
44+
;;
45+
*)
46+
fail "Unknown scenario: $SCENARIO"
47+
;;
48+
esac
49+
50+
# Run installer
51+
output="$(sh "$REPO_DIR/install.sh" 2>&1)" || fail "install.sh exited with error"
52+
echo "$output"
53+
54+
# Assertions
55+
case "$SCENARIO" in
56+
fresh)
57+
[ -f "$STATE_DIR/marketplace" ] || fail "Marketplace was not added"
58+
[ -f "$STATE_DIR/plugin" ] || fail "Plugin was not installed"
59+
echo "$output" | grep -q "Adding marketplace" || fail "Expected 'Adding marketplace' in output"
60+
echo "$output" | grep -q "Installing plugin" || fail "Expected 'Installing plugin' in output"
61+
pass "Fresh install succeeded"
62+
;;
63+
idempotent)
64+
echo "$output" | grep -q "Updating" || fail "Expected 'Updating' in output"
65+
echo "$output" | grep -q "v0.5.0" || fail "Expected version 0.5.0 in output"
66+
pass "Idempotent re-run succeeded"
67+
;;
68+
update)
69+
version="$(cat "$STATE_DIR/plugin_version")"
70+
[ "$version" = "0.5.0" ] || fail "Expected version 0.5.0 after update, got $version"
71+
echo "$output" | grep -q "v0.5.0" || fail "Expected version 0.5.0 in output"
72+
pass "Update from older version succeeded"
73+
;;
74+
esac
75+
76+
# Cleanup wrapper
77+
rm -f "$SCRIPT_DIR/claude"

0 commit comments

Comments
 (0)