Skip to content
Open
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
72 changes: 72 additions & 0 deletions devin_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,30 @@ def send_message_to_session(session_id: str, payload: dict) -> dict:
raise DevinAPIError(f"API request failed: {e}")


def upload_file_to_attachments(file_path: str) -> str:
"""Upload a file to Devin attachments and return the URL"""
api_key = get_api_key()

headers = {
'Authorization': f'Bearer {api_key}'
}

try:
with open(file_path, 'rb') as f:
response = requests.post(
'https://api.devin.ai/v1/attachments',
headers=headers,
files={'file': f},
timeout=30
)
response.raise_for_status()
return response.text.strip()
except requests.exceptions.RequestException as e:
raise DevinAPIError(f"API request failed: {e}")
except FileNotFoundError:
raise DevinAPIError(f"File not found: {file_path}")


def parse_list_input(value: str) -> List[str]:
"""Parse comma-separated string into list"""
if not value:
Expand Down Expand Up @@ -380,6 +404,54 @@ def message(session_id, message, output):
sys.exit(1)


@cli.command()
@click.argument('session_id')
@click.option('--image', '-i', 'file_path', help='Path to image or file to upload')
@click.option('--message', '-m', help='Optional message to send with the attachment')
@click.option('--output', '-o', type=click.Choice(['json', 'table']), default='table', help='Output format')
def upload(session_id, file_path, message, output):
"""Upload an image or file to an existing Devin session"""

if not file_path:
file_path = click.prompt('Path to file to upload', type=str)

if not os.path.exists(file_path):
click.echo(f"❌ Error: File not found: {file_path}", err=True)
sys.exit(1)

try:
click.echo(f"Uploading file {file_path}...")
file_url = upload_file_to_attachments(file_path)
click.echo(f"✅ File uploaded successfully")

if message:
message_text = f"{message}\n\nATTACHMENT:\"{file_url}\""
else:
message_text = f"ATTACHMENT:\"{file_url}\""

click.echo(f"Sending attachment to session {session_id}...")
payload = {'message': message_text}
result = send_message_to_session(session_id, payload)

if output == 'json':
result['file_url'] = file_url
click.echo(json.dumps(result, indent=2))
else:
click.echo("\n✅ Attachment sent successfully!")
click.echo(f"File URL: {file_url}")
if 'message_id' in result:
click.echo(f"Message ID: {result['message_id']}")
if 'status' in result:
click.echo(f"Session Status: {result['status']}")

except DevinAPIError as e:
click.echo(f"❌ Error: {e}", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"❌ Unexpected error: {e}", err=True)
sys.exit(1)


@cli.command()
@click.option('--target-dir', '-t', default='.', help='Target directory to copy files to (default: current directory)')
@click.option('--force', '-f', is_flag=True, help='Overwrite existing files without prompting')
Expand Down