Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions plugin/claude/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "dbxcli",
"displayName": "dbxcli",
"description": "Safely operate Dropbox through a locally installed dbxcli CLI, using its JSON manifest and schema-backed machine contract.",
"version": "0.1.0",
"author": {
"name": "Dropbox"
},
"repository": "https://github.com/dropbox/dbxcli",
"license": "Apache-2.0",
"keywords": ["dropbox", "cli", "files", "cloud-storage"],
"defaultEnabled": true
}
77 changes: 77 additions & 0 deletions plugin/claude/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# dbxcli plugin for Claude Code

A thin packaging layer that exposes the canonical `dbxcli` skill to Claude Code
as an installable plugin.

## What this plugin does

- Packages the portable `dbxcli` skill so Claude Code discovers it as
`/dbxcli:dbxcli`.
- Provides a Setup hook that detects whether `dbxcli` is installed and prints
platform-specific installation guidance if it is missing.

The plugin does not implement Dropbox API calls, maintain a command catalog, or
store credentials. The local `dbxcli` executable is the runtime, and its JSON
help manifest is the source of truth for available commands, flags, and schemas.

## Prerequisites

- **Claude Code** — the plugin host.
- **dbxcli** — installed and on `PATH`. The plugin detects its absence but does
not install it automatically.

Install dbxcli:

| Platform | Command |
|----------|---------|
| macOS | `brew install dbxcli` |
| Windows | `winget install --exact --id Dropbox.dbxcli` |
| Linux | Download from [releases](https://github.com/dropbox/dbxcli/releases) or `go install github.com/dropbox/dbxcli/v3@latest` |

For interactive use, authenticate with `dbxcli login`; automation may use
supported non-interactive authentication.

## How the canonical skill is included

The skill source of truth is `skills/dbxcli/` in the dbxcli repository. The
packaging script (`scripts/package-skills.sh`) copies that portable skill into
each platform artifact. Claude Code packaging adds only its manifest, hook,
script, and README; it does not own the skill's behavior.

## Structure

```
dist/claude/dbxcli-plugin/
├── .claude-plugin/plugin.json # Plugin manifest
├── skills/dbxcli/ # Copied from skills/dbxcli/
│ ├── SKILL.md
│ ├── agents/
│ └── references/
├── hooks/hooks.json # Setup hook
├── scripts/detect-dbxcli.sh # Detection script
└── README.md # This file
```

## Build

```bash
./scripts/package-skills.sh
```

## Test locally

Load the plugin without installing:

```bash
claude --plugin-dir dist/claude/dbxcli-plugin
```

Run the structural validation (no Dropbox auth required):

```bash
./scripts/test-plugin.sh
```

If dbxcli is installed, the test also verifies that the Setup hook detection
script runs correctly and that `dbxcli version --output=json` returns a valid
response.
14 changes: 14 additions & 0 deletions plugin/claude/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"hooks": {
"Setup": [
{
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/detect-dbxcli.sh\""
}
]
}
]
}
}
44 changes: 44 additions & 0 deletions plugin/claude/scripts/detect-dbxcli.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Detect dbxcli and print installation guidance if missing.
# This script is informational only — it never installs software.
set -euo pipefail

if command -v dbxcli >/dev/null 2>&1; then
version_json="$(dbxcli version --output=json 2>/dev/null)" || true

if printf '%s' "$version_json" | grep -q '"ok":true'; then
version="$(printf '%s' "$version_json" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4)"
echo "dbxcli ${version} is installed and responding."
exit 0
fi

echo "dbxcli is on PATH but did not return valid JSON."
echo "Try running: dbxcli version --output=json"
exit 0
fi

echo "dbxcli is not installed."
echo ""

case "$(uname -s)" in
Darwin)
echo "Install on macOS:"
echo " brew install dbxcli"
;;
Linux)
echo "Install on Linux (pick one):"
echo " • Download from https://github.com/dropbox/dbxcli/releases"
echo " • go install github.com/dropbox/dbxcli/v3@latest"
;;
MINGW*|MSYS*|CYGWIN*)
echo "Install on Windows:"
echo " winget install --exact --id Dropbox.dbxcli"
;;
*)
echo "Install from source or download a release:"
echo " https://github.com/dropbox/dbxcli/releases"
;;
esac

echo ""
echo "After installing, run: dbxcli version --output=json"
27 changes: 18 additions & 9 deletions scripts/package-skills.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ set -euo pipefail

root_dir="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
source_dir="$root_dir/skills/dbxcli"
plugin_dir="$root_dir/plugin/claude"
dist_dir="$root_dir/dist"
chatgpt_dir="$dist_dir/chatgpt"
claude_dir="$dist_dir/claude/dbxcli-plugin"
Expand All @@ -12,25 +13,33 @@ for required in "$source_dir/SKILL.md" "$source_dir/agents/openai.yaml"; do
[[ -f "$required" ]] || { echo "missing source file: $required" >&2; exit 1; }
done

for required in "$plugin_dir/.claude-plugin/plugin.json" \
"$plugin_dir/hooks/hooks.json" \
"$plugin_dir/scripts/detect-dbxcli.sh" \
"$plugin_dir/README.md"; do
[[ -f "$required" ]] || { echo "missing plugin file: $required" >&2; exit 1; }
done

rm -rf "$chatgpt_dir" "$claude_dir" "$openclaw_dir"
mkdir -p "$chatgpt_dir" "$claude_dir/.claude-plugin" "$claude_dir/skills" "$openclaw_dir/skills"
mkdir -p "$chatgpt_dir" "$claude_dir/skills" "$openclaw_dir/skills"

# ChatGPT: skill zip
cp -R "$source_dir" "$chatgpt_dir/dbxcli"
(
cd "$chatgpt_dir"
rm -f skill.zip
zip -qr skill.zip dbxcli
)

cp -R "$source_dir" "$claude_dir/skills/dbxcli"
cat > "$claude_dir/.claude-plugin/plugin.json" <<'EOF'
{
"name": "dbxcli",
"version": "0.1.0",
"description": "Safely operate Dropbox through a locally installed dbxcli CLI"
}
EOF
# Claude Code: plugin with skill, hooks, and scripts
cp -R "$plugin_dir/.claude-plugin" "$claude_dir/.claude-plugin"
cp -R "$plugin_dir/hooks" "$claude_dir/hooks"
cp -R "$plugin_dir/scripts" "$claude_dir/scripts"
cp "$plugin_dir/README.md" "$claude_dir/README.md"
cp -R "$source_dir" "$claude_dir/skills/dbxcli"
chmod +x "$claude_dir/scripts/detect-dbxcli.sh"

# OpenClaw: skill directory
cp -R "$source_dir" "$openclaw_dir/skills/dbxcli"
cat > "$openclaw_dir/openclaw.plugin.json" <<'EOF'
{
Expand Down
81 changes: 81 additions & 0 deletions scripts/test-plugin.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bash
# Validate the Claude Code dbxcli plugin structure and detection script.
# Does not require Dropbox authentication.
set -euo pipefail

root_dir="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
plugin_dir="$root_dir/dist/claude/dbxcli-plugin"
source_dir="$root_dir/skills/dbxcli"
pass=0
fail=0

check() {
local desc="$1"
shift
if "$@" >/dev/null 2>&1; then
echo " ok $desc"
pass=$((pass + 1))
else
echo " FAIL $desc"
fail=$((fail + 1))
fi
}

echo "=== Plugin structure ==="

check "plugin dist directory exists" test -d "$plugin_dir"
check "plugin.json exists" test -f "$plugin_dir/.claude-plugin/plugin.json"
check "plugin.json is valid JSON" python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$plugin_dir/.claude-plugin/plugin.json"
check "plugin.json has name field" python3 -c "
import json,sys
d = json.load(open(sys.argv[1]))
assert d.get('name') == 'dbxcli', 'name mismatch'
" "$plugin_dir/.claude-plugin/plugin.json"

check "hooks.json exists" test -f "$plugin_dir/hooks/hooks.json"
check "hooks.json is valid JSON" python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$plugin_dir/hooks/hooks.json"
check "hooks.json has Setup hook" python3 -c "
import json,sys
d = json.load(open(sys.argv[1]))
assert 'Setup' in d.get('hooks', {}), 'no Setup hook'
" "$plugin_dir/hooks/hooks.json"

check "detect-dbxcli.sh exists" test -f "$plugin_dir/scripts/detect-dbxcli.sh"
check "detect-dbxcli.sh is executable" test -x "$plugin_dir/scripts/detect-dbxcli.sh"
check "README.md exists" test -f "$plugin_dir/README.md"

echo ""
echo "=== Skill content ==="

check "skills/dbxcli/ directory exists" test -d "$plugin_dir/skills/dbxcli"
check "SKILL.md exists in packaged skill" test -f "$plugin_dir/skills/dbxcli/SKILL.md"
check "references/safety.md exists" test -f "$plugin_dir/skills/dbxcli/references/safety.md"
check "references/automation.md exists" test -f "$plugin_dir/skills/dbxcli/references/automation.md"
check "references/tool-integration.md exists" test -f "$plugin_dir/skills/dbxcli/references/tool-integration.md"

check "SKILL.md matches canonical source" diff -q "$source_dir/SKILL.md" "$plugin_dir/skills/dbxcli/SKILL.md"
check "safety.md matches canonical source" diff -q "$source_dir/references/safety.md" "$plugin_dir/skills/dbxcli/references/safety.md"

echo ""
echo "=== Host validation ==="

if command -v claude >/dev/null 2>&1; then
check "claude plugin validate passes" claude plugin validate "$plugin_dir"
else
echo " skip claude CLI not found — host validation skipped"
fi

echo ""
echo "=== Detection script ==="

output="$("$plugin_dir/scripts/detect-dbxcli.sh" 2>&1)" || true
if command -v dbxcli >/dev/null 2>&1; then
check "detect script finds installed dbxcli" grep -q "installed" <<< "$output"
check "dbxcli version --output=json returns ok" bash -c 'dbxcli version --output=json | python3 -c "import json,sys; assert json.load(sys.stdin)[\"ok\"]"'
else
check "detect script reports dbxcli missing" grep -q "not installed" <<< "$output"
fi

echo ""
echo "=== Results: $pass passed, $fail failed ==="
[[ $fail -eq 0 ]]
9 changes: 4 additions & 5 deletions skills/dbxcli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,10 @@ exit code and `.ok`:
For destructive or externally visible actions, first discover the command and
validate inputs, then prefer `--dry-run` if the manifest exposes it. Use an
explicit `--if-exists` policy whenever it is available; never assume that a
default overwrite or conflict policy matches the user's intent. Require clear
user confirmation before the real destructive action unless the user has
already explicitly requested the exact action. When a command exposes `--yes`,
use it only after that confirmation to prevent an interactive prompt from
blocking automation.
default overwrite or conflict policy matches the user's intent. A successful
dry-run does not authorize execution; obtain explicit user confirmation before
the real destructive action. When a command exposes `--yes`, use it only after
that confirmation to prevent an interactive prompt from blocking automation.

## Large listings, search, and multi-step work

Expand Down
19 changes: 19 additions & 0 deletions skills/dbxcli/references/automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@ Use `--dry-run` to preview a user-authorized mutation when it is offered. A
successful preview is not permission to perform the real action: obtain or use
the user's explicit confirmation for the real scope.

When a dry-run succeeds, it returns structured JSON with `ok: true` and
`results[].status` set to `"planned"`. Inspect all planned results before
proceeding:

- Check that every planned target matches the user's requested scope.
- Verify file vs folder types, target paths, and operation count.
- Do not proceed if planned scope is broader than requested.

Present a concise summary of the planned action to the user:

Planned: delete /Reports/old-file.txt (file, 2.1MB)
Method: move to Dropbox trash (recoverable)
No changes have been made yet.

Proceed with this deletion?

Obtain explicit confirmation. A `"planned"` status proves feasibility only; it
does not authorize execution.

When `--if-exists` is available, pass an explicit value. Typical policies are
`fail`, `skip`, and `autorename`; some commands also offer `overwrite`. Select
only a policy compatible with the user's stated intent. In particular, do not
Expand Down
16 changes: 16 additions & 0 deletions skills/dbxcli/references/safety.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,22 @@ changes, and creation or sharing of public links as meaningful external
effects. Scope them to the user's request, preview when available, and confirm
before executing the real mutation.

## Scope verification

Before executing a mutation, verify that the operation matches the exact scope
the user authorized. Check:

- Exact Dropbox paths or shared-link targets
- File vs folder type (if a path resolves to a folder when a file was expected, confirm)
- Single vs multiple targets (if dry-run shows more than expected, stop)
- Recursive behavior (do not add `--recursive` unless requested)
- Permanent vs recoverable deletion (do not add `--permanent` unless explicitly requested)
- Overwrite/conflict policy (use explicit `--if-exists`, do not silently choose `overwrite`)

Do not silently expand scope beyond the user's request. If the requested target,
operation mode, or affected path count differs from what the user described,
stop and confirm the actual scope before proceeding.

The public [security policy](https://github.com/dropbox/dbxcli/blob/master/SECURITY.md)
and [automation contract](https://github.com/dropbox/dbxcli/blob/master/docs/automation.md)
contain the authoritative protocol and credential details.