Initial actual-budget skill for Claude Code
Read-only Actual Budget API integration with shell helpers for querying accounts, transactions, budgets, categories, and spending.
This commit is contained in:
110
scripts/setup.sh
Executable file
110
scripts/setup.sh
Executable file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env bash
|
||||
# Setup wizard for Actual Budget skill
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_DIR="${HOME}/.config/actual-budget"
|
||||
CONFIG_FILE="${CONFIG_DIR}/config"
|
||||
PASSWORD_FILE="${CONFIG_DIR}/password"
|
||||
|
||||
echo "=== Actual Budget Skill Setup ==="
|
||||
echo ""
|
||||
|
||||
# Create config directory
|
||||
mkdir -p "${CONFIG_DIR}"
|
||||
|
||||
# Server URL
|
||||
read -rp "Actual Budget server URL [https://budget.prettyhefty.com]: " server_url
|
||||
server_url="${server_url:-https://budget.prettyhefty.com}"
|
||||
|
||||
# Password
|
||||
read -rsp "Server password: " password
|
||||
echo ""
|
||||
|
||||
if [[ -z "${password}" ]]; then
|
||||
echo "ERROR: Password is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Save config
|
||||
cat > "${CONFIG_FILE}" <<EOF
|
||||
ACTUAL_SERVER_URL="${server_url}"
|
||||
EOF
|
||||
|
||||
# Save password securely
|
||||
echo -n "${password}" > "${PASSWORD_FILE}"
|
||||
chmod 600 "${PASSWORD_FILE}"
|
||||
|
||||
echo ""
|
||||
echo "Config saved to ${CONFIG_FILE}"
|
||||
echo "Password saved to ${PASSWORD_FILE}"
|
||||
|
||||
# Install @actual-app/api locally
|
||||
echo ""
|
||||
echo "Installing @actual-app/api..."
|
||||
cd "${SCRIPT_DIR}"
|
||||
if [[ ! -f "${SCRIPT_DIR}/package.json" ]]; then
|
||||
npm init -y --silent > /dev/null 2>&1
|
||||
fi
|
||||
npm install @actual-app/api --silent 2>&1 | tail -1
|
||||
|
||||
# List available budgets and let user pick
|
||||
echo ""
|
||||
echo "Connecting to server and listing budgets..."
|
||||
budgets_json=$(node "${SCRIPT_DIR}/actual-query.mjs" budgets 2>&1) || {
|
||||
echo "ERROR: Failed to connect to server"
|
||||
echo "${budgets_json}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "Available budgets:"
|
||||
echo "${budgets_json}" | node -e "
|
||||
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf-8'));
|
||||
if (Array.isArray(data)) {
|
||||
data.forEach((b, i) => console.log(' ' + (i+1) + ') ' + (b.name || b.cloudFileId || b.id || 'unnamed') + ' [' + (b.cloudFileId || b.groupId || b.id || 'unknown') + ']'));
|
||||
} else {
|
||||
console.log(' (unexpected format)');
|
||||
console.log(JSON.stringify(data, null, 2));
|
||||
}
|
||||
"
|
||||
|
||||
echo ""
|
||||
read -rp "Enter budget sync ID (or number from list above): " sync_id
|
||||
|
||||
# If user entered a number, resolve it
|
||||
if [[ "${sync_id}" =~ ^[0-9]+$ ]]; then
|
||||
sync_id=$(echo "${budgets_json}" | node -e "
|
||||
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf-8'));
|
||||
const idx = ${sync_id} - 1;
|
||||
if (data[idx]) console.log(data[idx].cloudFileId || data[idx].groupId || data[idx].id || '');
|
||||
else console.log('');
|
||||
")
|
||||
fi
|
||||
|
||||
if [[ -z "${sync_id}" ]]; then
|
||||
echo "ERROR: Sync ID is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Append sync ID to config
|
||||
echo "ACTUAL_SYNC_ID=\"${sync_id}\"" >> "${CONFIG_FILE}"
|
||||
|
||||
echo ""
|
||||
echo "Testing connection..."
|
||||
test_result=$(node "${SCRIPT_DIR}/actual-query.mjs" accounts 2>&1) || {
|
||||
echo "ERROR: Failed to load budget"
|
||||
echo "${test_result}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
account_count=$(echo "${test_result}" | node -e "
|
||||
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf-8'));
|
||||
console.log(Array.isArray(data) ? data.length : 0);
|
||||
")
|
||||
|
||||
echo "Success! Found ${account_count} account(s)."
|
||||
echo ""
|
||||
echo "Setup complete. Source the helper to get started:"
|
||||
echo " source ${SCRIPT_DIR}/actual-helper.sh"
|
||||
echo " actual_accounts"
|
||||
Reference in New Issue
Block a user