diff --git a/bin/disinto b/bin/disinto index dc08953..69ac40a 100755 --- a/bin/disinto +++ b/bin/disinto @@ -2634,38 +2634,54 @@ disinto_hire_an_agent() { echo "Step 1: Creating user '${agent_name}' (if not exists)..." local user_exists=false + local user_pass="" + local admin_pass="" + + # Read admin password from .env for standalone runs (#184) + local env_file="${FACTORY_ROOT}/.env" + if [ -f "$env_file" ] && grep -q '^FORGE_ADMIN_PASS=' "$env_file" 2>/dev/null; then + admin_pass=$(grep '^FORGE_ADMIN_PASS=' "$env_file" | head -1 | cut -d= -f2-) + fi + + # Get admin token early (needed for both user creation and password reset) + local admin_user="disinto-admin" + admin_pass="${admin_pass:-admin}" + local admin_token="" + admin_token=$(curl -sf -X POST \ + -u "${admin_user}:${admin_pass}" \ + -H "Content-Type: application/json" \ + "${forge_url}/api/v1/users/${admin_user}/tokens" \ + -d '{"name":"temp-token","scopes":["all"]}' 2>/dev/null \ + | jq -r '.sha1 // empty') || admin_token="" + if [ -z "$admin_token" ]; then + admin_token=$(curl -sf \ + -u "${admin_user}:${admin_pass}" \ + "${forge_url}/api/v1/users/${admin_user}/tokens" 2>/dev/null \ + | jq -r '.[0].sha1 // empty') || admin_token="" + fi + if [ -z "$admin_token" ]; then + echo " Warning: could not obtain admin token, trying FORGE_TOKEN..." + admin_token="${FORGE_TOKEN}" + fi + if curl -sf --max-time 5 "${forge_url}/api/v1/users/${agent_name}" >/dev/null 2>&1; then user_exists=true echo " User '${agent_name}' already exists" - else - # Create user using admin token - local admin_user="disinto-admin" - local admin_pass="${_FORGE_ADMIN_PASS:-admin}" - - # Try to get admin token first - local admin_token - admin_token=$(curl -sf -X POST \ - -u "${admin_user}:${admin_pass}" \ + # Reset user password so we can get a token (#184) + user_pass="agent-$(head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 20)" + if curl -sf -X PATCH \ + -H "Authorization: token ${admin_token}" \ -H "Content-Type: application/json" \ - "${forge_url}/api/v1/users/${admin_user}/tokens" \ - -d '{"name":"temp-token","scopes":["all"]}' 2>/dev/null \ - | jq -r '.sha1 // empty') || admin_token="" - - if [ -z "$admin_token" ]; then - # Token might already exist — try listing - admin_token=$(curl -sf \ - -u "${admin_user}:${admin_pass}" \ - "${forge_url}/api/v1/users/${admin_user}/tokens" 2>/dev/null \ - | jq -r '.[0].sha1 // empty') || admin_token="" + "${forge_url}/api/v1/admin/users/${agent_name}" \ + -d "{\"password\":\"${user_pass}\"}" >/dev/null 2>&1; then + echo " Reset password for existing user '${agent_name}'" + else + echo " Warning: could not reset password for existing user" >&2 fi - - if [ -z "$admin_token" ]; then - echo " Warning: could not obtain admin token, trying FORGE_TOKEN..." - admin_token="${FORGE_TOKEN}" - fi - + else + # Create user using admin token (admin_token already obtained above) # Create the user - local user_pass="agent-$(head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 20)" + user_pass="agent-$(head -c 16 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 20)" if curl -sf -X POST \ -H "Authorization: token ${admin_token}" \ -H "Content-Type: application/json" \ @@ -2695,24 +2711,21 @@ disinto_hire_an_agent() { echo " Repo '${agent_name}/.profile' already exists" else # Get user token for creating repo + # Always try to get token using user_pass (set in Step 1 for new users, reset for existing) local user_token="" - if [ "$user_exists" = true ]; then - # Try to get token for the new user - # Note: user_pass was set in Step 1; for existing users this will fail (unknown password) - user_token=$(curl -sf -X POST \ - -u "${agent_name}:${user_pass}" \ - -H "Content-Type: application/json" \ - "${forge_url}/api/v1/users/${agent_name}/tokens" \ - -d "{\"name\":\".profile-repo-token\",\"scopes\":[\"repository\"]}" 2>/dev/null \ - | jq -r '.sha1 // empty') || user_token="" + user_token=$(curl -sf -X POST \ + -u "${agent_name}:${user_pass}" \ + -H "Content-Type: application/json" \ + "${forge_url}/api/v1/users/${agent_name}/tokens" \ + -d "{\"name\":\".profile-repo-token\",\"scopes\":[\"repository\"]}" 2>/dev/null \ + | jq -r '.sha1 // empty') || user_token="" - if [ -z "$user_token" ]; then - # Try listing existing tokens - user_token=$(curl -sf \ - -u "${agent_name}:${user_pass}" \ - "${forge_url}/api/v1/users/${agent_name}/tokens" 2>/dev/null \ - | jq -r '.[0].sha1 // empty') || user_token="" - fi + if [ -z "$user_token" ]; then + # Try listing existing tokens + user_token=$(curl -sf \ + -u "${agent_name}:${user_pass}" \ + "${forge_url}/api/v1/users/${agent_name}/tokens" 2>/dev/null \ + | jq -r '.[0].sha1 // empty') || user_token="" fi # Fall back to admin token if user token not available @@ -2721,26 +2734,45 @@ disinto_hire_an_agent() { user_token="${admin_token:-${FORGE_TOKEN}}" fi - # Create the repo - if curl -sf -X POST \ - -H "Authorization: token ${user_token}" \ - -H "Content-Type: application/json" \ - "${forge_url}/api/v1/user/repos" \ - -d "{\"name\":\".profile\",\"description\":\"${agent_name}'s .profile repo\",\"private\":true,\"auto_init\":false}" >/dev/null 2>&1; then - echo " Created repo '${agent_name}/.profile'" - else - # Try with org path - if curl -sf -X POST \ + # Create the repo using the user's namespace (user/repos with user_token creates in that user's namespace) + # or use admin API to create in specific user's namespace + local repo_created=false + local create_output + + if [ -n "$user_token" ]; then + # Try creating as the agent user (user token creates in that user's namespace) + create_output=$(curl -sf -X POST \ -H "Authorization: token ${user_token}" \ -H "Content-Type: application/json" \ - "${forge_url}/api/v1/orgs/${agent_name}/repos" \ - -d "{\"name\":\".profile\",\"description\":\"${agent_name}'s .profile repo\",\"private\":true,\"auto_init\":false}" >/dev/null 2>&1; then - echo " Created repo '${agent_name}/.profile' (in org)" - else - echo " Error: failed to create repo '${agent_name}/.profile'" >&2 - exit 1 + "${forge_url}/api/v1/user/repos" \ + -d "{\"name\":\".profile\",\"description\":\"${agent_name}'s .profile repo\",\"private\":true,\"auto_init\":false}" 2>&1) || true + + if echo "$create_output" | grep -q '"id":\|[0-9]'; then + repo_created=true + echo " Created repo '${agent_name}/.profile'" fi fi + + # If user token failed or wasn't available, use admin API to create in agent's namespace + if [ "$repo_created" = false ]; then + echo " Using admin API to create repo in ${agent_name}'s namespace" + create_output=$(curl -sf -X POST \ + -H "Authorization: token ${user_token}" \ + -H "Content-Type: application/json" \ + "${forge_url}/api/v1/admin/users/${agent_name}/repos" \ + -d "{\"name\":\".profile\",\"description\":\"${agent_name}'s .profile repo\",\"private\":true,\"auto_init\":false}" 2>&1) || true + + if echo "$create_output" | grep -q '"id":\|[0-9]'; then + repo_created=true + echo " Created repo '${agent_name}/.profile' (via admin API)" + fi + fi + + if [ "$repo_created" = false ]; then + echo " Error: failed to create repo '${agent_name}/.profile'" >&2 + echo " Response: ${create_output}" >&2 + exit 1 + fi fi # Step 3: Clone repo and create initial commit @@ -2751,23 +2783,28 @@ disinto_hire_an_agent() { rm -rf "$clone_dir" mkdir -p "$clone_dir" - # Build clone URL (unauthenticated version for display) + # Build authenticated clone URL + # Use user_token if available, otherwise fall back to FORGE_TOKEN + local clone_token="${user_token:-${FORGE_TOKEN}}" + if [ -z "$clone_token" ]; then + echo " Error: no authentication token available for cloning" >&2 + exit 1 + fi + local clone_url="${forge_url}/${agent_name}/.profile.git" local auth_url - auth_url=$(printf '%s' "$forge_url" | sed "s|://|://${agent_name}:${user_token:-${FORGE_TOKEN}}@|") - clone_url="${auth_url}/.profile.git" + auth_url=$(printf '%s' "$forge_url" | sed "s|://|://${agent_name}:${clone_token}@|") + auth_url="${auth_url}/.profile.git" # Display unauthenticated URL (auth token only in actual git clone command) echo " Cloning: ${forge_url}/${agent_name}/.profile.git" - if ! git clone --quiet "$clone_url" "$clone_dir" 2>/dev/null; then - # Try without auth (might work for public repos or with FORGE_TOKEN) - clone_url="${forge_url}/${agent_name}/.profile.git" - if ! git clone --quiet "$clone_url" "$clone_dir" 2>/dev/null; then - echo " Error: failed to clone repo" >&2 - rm -rf "$clone_dir" - exit 1 - fi + # Try authenticated clone first (required for private repos) + if ! git clone --quiet "$auth_url" "$clone_dir" 2>/dev/null; then + echo " Error: failed to clone repo with authentication" >&2 + echo " Note: Ensure the user has a valid API token with repository access" >&2 + rm -rf "$clone_dir" + exit 1 fi # Configure git