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
18 changes: 18 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,24 @@ jobs:
if ($actual -ne $expected) {
throw "WingetCreate checksum mismatch: expected $expected, got $actual"
}
- name: Sync WinGet submission fork
shell: pwsh
env:
WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_CREATE_GITHUB_TOKEN }}
run: |
if ([string]::IsNullOrWhiteSpace($env:WINGET_CREATE_GITHUB_TOKEN)) {
throw "The WINGET_CREATE_GITHUB_TOKEN repository secret is not configured"
}

$headers = @{
Accept = "application/vnd.github+json"
Authorization = "Bearer $env:WINGET_CREATE_GITHUB_TOKEN"
"X-GitHub-Api-Version" = "2022-11-28"
}
$response = Invoke-RestMethod -Method Post -Headers $headers `
-Uri "https://api.github.com/repos/AndreyVMarkelov/winget-pkgs/merge-upstream" `
-ContentType "application/json" -Body '{"branch":"master"}'
Write-Host "WinGet fork sync: $($response.message)"
- name: Submit WinGet manifest
shell: pwsh
env:
Expand Down
47 changes: 47 additions & 0 deletions scripts/package-skills.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail

root_dir="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)"
source_dir="$root_dir/skills/dbxcli"
dist_dir="$root_dir/dist"
chatgpt_dir="$dist_dir/chatgpt"
claude_dir="$dist_dir/claude/dbxcli-plugin"
openclaw_dir="$dist_dir/openclaw/dbxcli-plugin"

for required in "$source_dir/SKILL.md" "$source_dir/agents/openai.yaml"; do
[[ -f "$required" ]] || { echo "missing source 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"

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

cp -R "$source_dir" "$openclaw_dir/skills/dbxcli"
cat > "$openclaw_dir/openclaw.plugin.json" <<'EOF'
{
"id": "dbxcli",
"name": "dbxcli",
"version": "0.1.0",
"description": "Safely operate Dropbox through a locally installed dbxcli CLI",
"skills": ["skills"]
}
EOF

echo "built $chatgpt_dir/skill.zip"
echo "built $claude_dir"
echo "built $openclaw_dir"
57 changes: 57 additions & 0 deletions scripts/test-skill-contract.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
set -euo pipefail

dbxcli_bin="${1:-dbxcli}"
if ! command -v "$dbxcli_bin" >/dev/null 2>&1 && [[ ! -x "$dbxcli_bin" ]]; then
echo "dbxcli executable not found: $dbxcli_bin" >&2
exit 1
fi

assert_json() {
local json="$1"
shift
jq -e "$@" >/dev/null <<<"$json" || { echo "JSON assertion failed" >&2; exit 1; }
}

version="$($dbxcli_bin version --output=json)"
assert_json "$version" '.ok == true and .command == "version"'

root_help="$($dbxcli_bin --help --output=json)"
assert_json "$root_help" '.ok == true and (.results | length > 0)'

put_help="$($dbxcli_bin put --help --output=json)"
assert_json "$put_help" '.ok == true and .results[0].result.input_schema.type == "object"'
assert_json "$put_help" '.results[0].result.supports_structured_output == true'
assert_json "$put_help" '.results[0].result.input_schema.properties.if_exists["x-cli-name"] == "if-exists"'

planned_put='{"source":"report.md","target":"/Reports/report.md","if_exists":"fail","dry_run":true}'
assert_json "$put_help" --argjson planned "$planned_put" '
.results[0].result.input_schema as $schema |
($planned | keys | all(. as $key | $schema.properties[$key] != null)) and
$schema.properties.if_exists["x-cli-name"] == "if-exists" and
$schema.properties.dry_run["x-cli-name"] == "dry-run" and
$schema.properties.dry_run.type == "boolean"
'

rm_help="$($dbxcli_bin rm --help --output=json)"
assert_json "$rm_help" '.ok == true and ([.results[0].result.flags[] | select(.name == "dry-run")] | length == 1)'
assert_json "$rm_help" '.results[0].result.destructive_level != "none"'

ls_help="$($dbxcli_bin ls --help --output=json)"
assert_json "$ls_help" '.ok == true and .results[0].result.input_schema.properties.limit["x-cli-name"] == "limit"'

search_help="$($dbxcli_bin search --help --output=json)"
assert_json "$search_help" '.ok == true and .results[0].result.input_schema.properties.content["x-cli-name"] == "content"'
assert_json "$search_help" '.results[0].result.input_schema.properties.path_scope["x-cli-name"] == "path-scope"'

login_help="$($dbxcli_bin login --help --output=json)"
assert_json "$login_help" '.ok == true and .results[0].result.supports_structured_output == false'

set +e
invalid="$($dbxcli_bin ls --output=json --not-a-flag / 2>/dev/null)"
exit_code=$?
set -e
[[ $exit_code -eq 7 ]] || { echo "expected unknown flag to exit 7, got $exit_code" >&2; exit 1; }
assert_json "$invalid" '.ok == false and .error.code == "unknown_flag"'

echo "dbxcli skill contract checks passed (13 assertions)"
93 changes: 93 additions & 0 deletions skills/dbxcli/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
name: dbxcli
description: Safely operate Dropbox through a locally installed dbxcli command, using its JSON manifest and schema-backed machine contract. Use for Dropbox file, shared-link, team, or account work; do not call the Dropbox API directly.
---

# dbxcli

Use the local `dbxcli` executable as the only Dropbox integration. Do not
reimplement Dropbox API calls, scrape text help, or maintain a command catalog
in this skill. The CLI's JSON help manifest is authoritative for the installed
version.

## Start safely

1. Check availability with `command -v dbxcli`, then run
`dbxcli version --output=json`. If it is unavailable, say so and give
installation guidance for the user's operating system, using the
[dbxcli releases](https://github.com/dropbox/dbxcli/releases) page. Do not
download or install it unless the user authorizes that action.
2. Before a command you have not already discovered in the current task, run
`dbxcli [command path] --help --output=json`. Begin with
`dbxcli --help --output=json` when the command path is unknown.
3. Read the manifest's `supports_structured_output`, `input_schema`,
`stdin_stdout`, `destructive_level`, `flags`, and `args`. Do not infer a
command or flag from memory.
4. Represent the intended arguments and flags as JSON-shaped input and validate
it against that command's `input_schema` before building the shell command.
Map fields to command-line names using each field's `x-cli-name`.

Read [tool-integration.md](references/tool-integration.md) for the discovery,
validation, result, and error protocol. Read [automation.md](references/automation.md)
for writes and confirmation behavior. Read [safety.md](references/safety.md)
before handling credentials, transfers, deletion, replacement, or sharing.

## Execution contract

For normal command execution, always pass `--output=json` and parse stdout as a
single JSON envelope. Treat stderr as diagnostics only. Check both the process
exit code and `.ok`:

- If `.ok` is `true`, use documented `results[].status`, `results[].kind`, and
`warnings`; do not rely on prose or undocumented fields.
- If `.ok` is `false`, branch on stable `.error.code`, not `.error.message`.
Surface a concise, redacted explanation and use structured `.error.details`
only when relevant. Do not blindly retry writes or auth errors.
- If the manifest says `supports_structured_output: false`, do not run that
command as a machine-action. Explain the limitation or use a safe supported
alternative. JSON help itself remains available.

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.

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

The CLI follows Dropbox pagination internally; agents must not invent or pass
cursors. For a broad `ls` or `search`, discover the command and use its
`--limit` flag to bound the result delivered to the tool. Start with the
narrowest sensible folder or search path; do not recursively enumerate a whole
Dropbox when a scoped query will answer the request. A limited result is a
selection, not proof that no additional matches exist.

For search requests about text *inside* files, inspect the `search` manifest
and pass `--content` only when it is available and the user requested a
content search. Otherwise search is filename-oriented. Scope the search path
and limit whenever practical.

For a search → get → process task: discover and validate `search`, select the
exact result path from its JSON metadata, then discover and validate `get`.
Download to a named local file (never stdout), process that local file, and
report the output path or a concise result. A later upload, share-link, or
replacement is a separate externally visible action and needs its own
discovery, safety policy, and authorization.

## Boundaries

- Never put tokens, auth codes, app secrets, environment dumps, or auth-file
contents in prompts, commands, logs, JSON fixtures, tool results, commits,
or artifacts. Refer to secret names and paths only when needed.
- Do not use `DBXCLI_ACCESS_TOKEN=value` inline. Pass an already-provisioned
secret through the execution environment. Keep `DBXCLI_AUTH_FILE` outside
the repository and do not read, upload, or commit it.
- Never send binary file data through a tool result. For `get` or
`share-link download`, download to a local file and report its path and
metadata. `local operand -` is a byte stream and cannot be combined with
JSON output.
- Do not use this skill to expose shared links, alter permissions, overwrite,
move, restore, or delete without user-authorized scope.
6 changes: 6 additions & 0 deletions skills/dbxcli/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
interface:
display_name: "dbxcli"
short_description: "Safely operate Dropbox with dbxcli"
default_prompt: "Use $dbxcli to inspect Dropbox safely through the local CLI."
policy:
allow_implicit_invocation: true
29 changes: 29 additions & 0 deletions skills/dbxcli/references/automation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Write operations and interaction

Discover the specific command first. The manifest tells you whether it supports
`--dry-run`, `--if-exists`, `--yes`, structured output, and prompts. These flags
are command-specific; never attach one speculatively.

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 `--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
silently choose `overwrite`.

Use `--yes` only after confirmation has been established and only if the
discovered manifest exposes it. It acknowledges an operation; it does not
replace authorization.

Before an automated job that needs Dropbox access, use
`dbxcli account --output=json` as an auth and identity check. Prefer a
short-lived, pre-provisioned `DBXCLI_ACCESS_TOKEN` in the execution environment
for CI. When saved credentials are required, set `DBXCLI_AUTH_FILE` to a
private secret-backed or temporary location outside the repository. Do not
commit, cache, upload, or print that file.

The full public source is
[Automation and JSON output](https://github.com/dropbox/dbxcli/blob/master/docs/automation.md);
this reference intentionally does not duplicate its command catalog or schema.
25 changes: 25 additions & 0 deletions skills/dbxcli/references/safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Safety and data handling

Treat tokens, refresh tokens, authorization codes, app secrets, and auth files
as secrets. Never ask users to paste them into a chat or command line; do not
read or display an auth file. Avoid environment dumps and shell tracing. Redact
any secret accidentally present in command output before reporting it.

`dbxcli get <remote> -` and `dbxcli share-link download <url> -` write raw
bytes to stdout. They cannot use `--output=json`. Do not use these forms when a
tool captures stdout, because binary data can corrupt a tool result or consume
context. Use a named local destination instead, then report only safe metadata
such as the destination path, size, and checksum if necessary.

Likewise, do not upload binary data into a chat transcript. For a local source,
pass its path to the CLI. For a generated stream, use a pipe only when the
execution environment will not return those bytes as a tool result.

Treat delete, overwrite, move, restore, permission changes, team member
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.

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.
55 changes: 55 additions & 0 deletions skills/dbxcli/references/tool-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Machine-contract integration

The installed CLI, not this reference, owns command discovery. JSON help works
without Dropbox authentication:

```sh
dbxcli --help --output=json
dbxcli put --help --output=json
dbxcli share-link create --help --output=json
```

Each help result describes one command. Inspect
`results[].result.supports_structured_output` before normal execution. Its
`input_schema` is JSON Schema for positional arguments and flags. It uses
JSON-friendly names (for example `if_exists`) and retains the CLI spelling in
`x-cli-name`; validate an intended JSON input against it before constructing an
invocation.

Run supported operations with `--output=json`. Stdout is exactly one JSON
success or error envelope; stderr can contain progress, warnings, and
diagnostics. Never parse text output as a fallback.

```sh
dbxcli ls --output=json /
dbxcli put --if-exists fail --output=json report.md /Reports/report.md
```

Success has `ok: true`, `schema_version`, `command`, `input`, `results`, and
`warnings`. Use `results[].status` and `results[].kind` as the stable outcome.
An error has `ok: false` and an `error` object. Check the shell exit status as
well as `error.code`: the latter is the stable remediation key; message text is
human-facing and may change. Known error details are structured context, not a
license to expose sensitive values.

Common exit-code classes: auth (2), permission (3), not found (4), conflict
(5), rate limit (6), validation/unsupported structured output (7), and partial
stdout transfer (8). A rate-limit response may contain
`error.details.retry_after_seconds`; wait only when the user task remains safe
to retry. Never automatically retry a non-idempotent or destructive operation.

`ls` and `search` can span multiple Dropbox result pages, but dbxcli retrieves
those pages internally. Do not create a cursor loop in the agent. Use the
command's discovered `--limit` to bound tool output; a limit caps returned
items and does not establish that the full Dropbox result set has been seen.

For deep search, the discovered `search` manifest exposes `--content` when the
installed CLI supports it. It searches file contents in addition to filenames.
Use it only when the request is specifically about content, and use the
optional Dropbox path scope to avoid a broad account-wide search.

For schema-level validation of help and results, use the public
[JSON schema v1 documentation](https://github.com/dropbox/dbxcli/blob/master/docs/json-schema/v1/README.md):
`manifest.schema.json`, `commands.schema.json`, and `error.schema.json`. Pin a
release tag rather than `master` when a wrapper needs reproducible remote
schema URLs.