diff --git a/.codacy/cli.sh b/.codacy/cli.sh new file mode 100755 index 00000000..7057e3bf --- /dev/null +++ b/.codacy/cli.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env bash + + +set -e +o pipefail + +# Set up paths first +bin_name="codacy-cli-v2" + +# Determine OS-specific paths +os_name=$(uname) +arch=$(uname -m) + +case "$arch" in +"x86_64") + arch="amd64" + ;; +"x86") + arch="386" + ;; +"aarch64"|"arm64") + arch="arm64" + ;; +esac + +if [ -z "$CODACY_CLI_V2_TMP_FOLDER" ]; then + if [ "$(uname)" = "Linux" ]; then + CODACY_CLI_V2_TMP_FOLDER="$HOME/.cache/codacy/codacy-cli-v2" + elif [ "$(uname)" = "Darwin" ]; then + CODACY_CLI_V2_TMP_FOLDER="$HOME/Library/Caches/Codacy/codacy-cli-v2" + else + CODACY_CLI_V2_TMP_FOLDER=".codacy-cli-v2" + fi +fi + +version_file="$CODACY_CLI_V2_TMP_FOLDER/version.yaml" + + +get_version_from_yaml() { + if [ -f "$version_file" ]; then + local version=$(grep -o 'version: *"[^"]*"' "$version_file" | cut -d'"' -f2) + if [ -n "$version" ]; then + echo "$version" + return 0 + fi + fi + return 1 +} + +get_latest_version() { + local response + if [ -n "$GH_TOKEN" ]; then + response=$(curl -Lq --header "Authorization: Bearer $GH_TOKEN" "https://api.github.com/repos/codacy/codacy-cli-v2/releases/latest" 2>/dev/null) + else + response=$(curl -Lq "https://api.github.com/repos/codacy/codacy-cli-v2/releases/latest" 2>/dev/null) + fi + + handle_rate_limit "$response" + local version=$(echo "$response" | grep -m 1 tag_name | cut -d'"' -f4) + echo "$version" +} + +handle_rate_limit() { + local response="$1" + if echo "$response" | grep -q "API rate limit exceeded"; then + fatal "Error: GitHub API rate limit exceeded. Please try again later" + fi +} + +download_file() { + local url="$1" + + echo "Downloading from URL: ${url}" + if command -v curl > /dev/null 2>&1; then + curl -# -LS "$url" -O + elif command -v wget > /dev/null 2>&1; then + wget "$url" + else + fatal "Error: Could not find curl or wget, please install one." + fi +} + +download() { + local url="$1" + local output_folder="$2" + + ( cd "$output_folder" && download_file "$url" ) +} + +download_cli() { + # OS name lower case + suffix=$(echo "$os_name" | tr '[:upper:]' '[:lower:]') + + local bin_folder="$1" + local bin_path="$2" + local version="$3" + + if [ ! -f "$bin_path" ]; then + echo "đŸ“Ĩ Downloading CLI version $version..." + + remote_file="codacy-cli-v2_${version}_${suffix}_${arch}.tar.gz" + url="https://github.com/codacy/codacy-cli-v2/releases/download/${version}/${remote_file}" + + download "$url" "$bin_folder" + tar xzfv "${bin_folder}/${remote_file}" -C "${bin_folder}" + fi +} + +# Warn if CODACY_CLI_V2_VERSION is set and update is requested +if [ -n "$CODACY_CLI_V2_VERSION" ] && [ "$1" = "update" ]; then + echo "âš ī¸ Warning: Performing update with forced version $CODACY_CLI_V2_VERSION" + echo " Unset CODACY_CLI_V2_VERSION to use the latest version" +fi + +# Ensure version.yaml exists and is up to date +if [ ! -f "$version_file" ] || [ "$1" = "update" ]; then + echo "â„šī¸ Fetching latest version..." + version=$(get_latest_version) + mkdir -p "$CODACY_CLI_V2_TMP_FOLDER" + echo "version: \"$version\"" > "$version_file" +fi + +# Set the version to use +if [ -n "$CODACY_CLI_V2_VERSION" ]; then + version="$CODACY_CLI_V2_VERSION" +else + version=$(get_version_from_yaml) +fi + + +# Set up version-specific paths +bin_folder="${CODACY_CLI_V2_TMP_FOLDER}/${version}" + +mkdir -p "$bin_folder" +bin_path="$bin_folder"/"$bin_name" + +# Download the tool if not already installed +download_cli "$bin_folder" "$bin_path" "$version" +chmod +x "$bin_path" + +run_command="$bin_path" +if [ -z "$run_command" ]; then + fatal "Codacy cli v2 binary could not be found." +fi + +if [ "$#" -eq 1 ] && [ "$1" = "download" ]; then + echo "Codacy cli v2 download succeeded" +else + eval "$run_command $*" +fi \ No newline at end of file diff --git a/.codacy/codacy.yaml b/.codacy/codacy.yaml new file mode 100644 index 00000000..98315297 --- /dev/null +++ b/.codacy/codacy.yaml @@ -0,0 +1,15 @@ +runtimes: + - dart@3.7.2 + - go@1.24.13 + - java@17.0.10 + - node@22.2.0 + - python@3.11.11 +tools: + - dartanalyzer@3.7.2 + - eslint@8.57.0 + - lizard@1.17.31 + - opengrep@1.16.4 + - pmd@7.11.0 + - pylint@3.3.6 + - revive@1.7.0 + - trivy@0.69.3 diff --git a/assets/skills/amazon-ordering/SKILL.md b/assets/skills/amazon-ordering/SKILL.md new file mode 100644 index 00000000..cf923d53 --- /dev/null +++ b/assets/skills/amazon-ordering/SKILL.md @@ -0,0 +1,184 @@ +--- +name: amazon +description: Buy and return items on Amazon using browser automation. Use for purchasing, reordering, checking order history, and processing returns. +metadata: {"openclaw":{"skillKey":"amazon-ordering"}} +--- + +# Amazon Ordering + +Use this skill when the user wants an agent to buy something on Amazon, reorder a previous item, inspect Amazon order history, or start a return. + +## Trigger + +```json +{ + "activation": { + "anyPhrases": [ + "amazon", + "buy on amazon", + "order on amazon", + "reorder on amazon", + "amazon order history", + "amazon return" + ] + }, + "movement": { + "target": "shop", + "skipIfAlreadyThere": true + } +} +``` + +When this skill is activated, the agent should walk to the office shop before handling the request. + +- Treat requests from Telegram or any other external surface as valid triggers when they ask for Amazon purchasing, reordering, order lookup, or return processing. +- The physical behavior for this skill is: go to the shop, connect to the browser session, then perform the Amazon workflow. +- If the agent is already at the shop, continue without adding extra movement narration. + +## Prerequisites + +- `agent-browser` CLI installed. +- Chrome running with remote debugging enabled on port `9222`. +- Logged into Amazon. If logged out, retrieve the password from the configured password manager or ask the user to complete login. +- If running headless on Linux, use VNC or another visual path so the user can solve CAPTCHAs or 2FA when needed. + +## Setup + +Read `skills/amazon-ordering/defaults.json` before making a purchase or processing a return. Use these saved workspace defaults: + +```json +{ + "shippingAddress": "123 Main St, Springfield, IL 62704", + "paymentMethod": "Visa ending in 4242", + "returnDropoff": "Whole Foods" +} +``` + +If `defaults.json` is missing, fall back to these environment variables: + +```bash +export AMAZON_SHIPPING_ADDRESS="Your shipping address" +export AMAZON_PAYMENT_METHOD="Your preferred card" +export AMAZON_RETURN_DROPOFF="Whole Foods" +``` + +Always verify the shipping address and payment method before placing an order. + +## Communication rules + +- Do not narrate each click or intermediate browser step. +- Only message the user when you need clarification, confirmation, or manual intervention. +- If Amazon shows CAPTCHA, MFA, login recovery, or an unexpected checkout change, pause and ask the user to help. +- When a purchase, reorder, or return is confirmed, reply with a brief summary of the result. + +## Ordering rules + +### Reorders + +- Go directly to Amazon order history and search for the item. +- Use "Buy it again" when the correct previous item is found. +- Verify the selected address and payment method. +- Place the order once the item clearly matches the user's request. +- No screenshot is required for a confirmed reorder unless something looks ambiguous. + +### New items + +- Search or navigate to the requested product. +- Before adding to cart, send the user a screenshot that clearly shows the product image and price. +- Wait for explicit user confirmation before continuing with a new item purchase. +- Verify the address and payment method again on checkout before placing the order. + +### Order history and status + +- Use order history for lookup, reorder decisions, and return eligibility checks. +- When the user asks about past purchases, summarize only the relevant orders instead of dumping the full page. +- If multiple similar orders match, ask which one they mean before acting. + +## Return defaults + +Use these defaults unless the user says otherwise: + +- Return reason: "Changed Mind" -> "My needs changed". +- Packaging opened: Yes. +- Item in original packaging: Yes. +- Have you used the item: Yes. +- Signs of use: None. +- Battery leaks or overheating: No. +- All accessories included: Yes. +- Refund type: Refund to original payment method. +- Drop-off location: Use `returnDropoff` from `defaults.json`, or `AMAZON_RETURN_DROPOFF`, or Whole Foods. + +## Return flow + +1. Open order history and find the item. +2. Choose "Return or replace items". +3. Select "Changed Mind" and then "My needs changed". +4. Answer condition questions with the defaults in this document unless the user gave different facts. +5. Continue past support suggestions. +6. Select refund to the original payment method. +7. Select the preferred drop-off location. +8. Confirm the return. + +After a successful return, send only a short confirmation that includes the item name, refund amount, and drop-off location plus deadline if Amazon shows one. + +## Browser workflow + +### Connect to the browser + +```bash +agent-browser connect 9222 +``` + +Always open a new tab because other sessions may share the same Chrome profile. Use `--new-tab` on every open command. + +### Search order history + +```bash +agent-browser open --new-tab "https://www.amazon.com/gp/your-account/order-history" +agent-browser snapshot -i +``` + +Use the order-history search box to find the relevant item. + +### Reorder flow + +```bash +agent-browser click @[buy-it-again-ref] +agent-browser snapshot -i +``` + +Verify the address and payment method before clicking the place-order control. + +### Screenshot guidance + +- Scroll past Amazon navigation bars before taking the screenshot. +- Ensure the product image and current price are both visible. +- Save the screenshot to a temporary location and send it with a short caption. + +## Starting the browser if needed + +### Linux desktop session + +```bash +google-chrome --user-data-dir=$HOME/.config/chrome-agent --no-first-run --remote-debugging-port=9222 https://www.amazon.com & +``` + +### Linux headless session + +```bash +DISPLAY=:99 google-chrome --user-data-dir=$HOME/.config/chrome-agent --no-first-run --remote-debugging-port=9222 https://www.amazon.com & +``` + +### macOS + +```bash +open -na "Google Chrome" --args --user-data-dir=$HOME/.config/chrome-agent --no-first-run --remote-debugging-port=9222 https://www.amazon.com +``` + +## Notes + +- The browser profile typically persists login at `$HOME/.config/chrome-agent`. +- Workspace defaults live in `skills/amazon-ordering/defaults.json`. +- Order confirmations go to the email on the Amazon account. +- If the user asks for a new purchase but has not confirmed the item screenshot yet, do not place the order. +- If the user asks for a return, use the silent return workflow and only surface the final result unless clarification is required. diff --git a/src/features/office/components/panels/PackagedSkillSetupModal.tsx b/src/features/office/components/panels/PackagedSkillSetupModal.tsx new file mode 100644 index 00000000..a0d9887c --- /dev/null +++ b/src/features/office/components/panels/PackagedSkillSetupModal.tsx @@ -0,0 +1,160 @@ +"use client"; + +import { useEffect, useMemo, useState } from "react"; + +import type { + PackagedSkillSetupDefinition, + PackagedSkillSetupValues, +} from "@/lib/skills/packaged-setup"; + +type PackagedSkillSetupModalProps = { + skillName: string; + definition: PackagedSkillSetupDefinition; + initialValues?: PackagedSkillSetupValues; + busy?: boolean; + errorMessage?: string | null; + onClose: () => void; + onSubmit: (values: PackagedSkillSetupValues) => Promise | void; +}; + +export function PackagedSkillSetupModal({ + skillName, + definition, + initialValues, + busy = false, + errorMessage = null, + onClose, + onSubmit, +}: PackagedSkillSetupModalProps) { + const [values, setValues] = useState(() => initialValues ?? {}); + + useEffect(() => { + setValues(initialValues ?? {}); + }, [definition, initialValues, skillName]); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key !== "Escape" || busy) { + return; + } + event.preventDefault(); + onClose(); + }; + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, [busy, onClose]); + + const missingRequiredField = useMemo( + () => + definition.fields.find((field) => field.required && !(values[field.key] ?? "").trim()) ?? null, + [definition.fields, values] + ); + + return ( +
{ + if (!busy) { + onClose(); + } + }} + > +
event.stopPropagation()} + onSubmit={(event) => { + event.preventDefault(); + if (busy || missingRequiredField) { + return; + } + void Promise.resolve(onSubmit(values)).catch(() => undefined); + }} + > +
+ Install setup +
+
+ {definition.title} for {skillName} +
+
{definition.description}
+ +
+ {definition.fields.map((field) => { + const value = values[field.key] ?? ""; + return ( +