diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25b12e4..8db4d13 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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: diff --git a/scripts/package-skills.sh b/scripts/package-skills.sh new file mode 100755 index 0000000..2ca7cf0 --- /dev/null +++ b/scripts/package-skills.sh @@ -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" diff --git a/scripts/test-skill-contract.sh b/scripts/test-skill-contract.sh new file mode 100755 index 0000000..eaa658f --- /dev/null +++ b/scripts/test-skill-contract.sh @@ -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)" diff --git a/skills/dbxcli/SKILL.md b/skills/dbxcli/SKILL.md new file mode 100644 index 0000000..3354abb --- /dev/null +++ b/skills/dbxcli/SKILL.md @@ -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. diff --git a/skills/dbxcli/agents/openai.yaml b/skills/dbxcli/agents/openai.yaml new file mode 100644 index 0000000..5848613 --- /dev/null +++ b/skills/dbxcli/agents/openai.yaml @@ -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 diff --git a/skills/dbxcli/references/automation.md b/skills/dbxcli/references/automation.md new file mode 100644 index 0000000..b3d6ab5 --- /dev/null +++ b/skills/dbxcli/references/automation.md @@ -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. diff --git a/skills/dbxcli/references/safety.md b/skills/dbxcli/references/safety.md new file mode 100644 index 0000000..cc4cd3c --- /dev/null +++ b/skills/dbxcli/references/safety.md @@ -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 -` and `dbxcli share-link download -` 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. diff --git a/skills/dbxcli/references/tool-integration.md b/skills/dbxcli/references/tool-integration.md new file mode 100644 index 0000000..92ab78c --- /dev/null +++ b/skills/dbxcli/references/tool-integration.md @@ -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.