devin-cli 1.4.0__tar.gz → 1.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devin_cli-1.4.0 → devin_cli-1.5.1}/.gitignore +1 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/CHANGELOG.md +29 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/PKG-INFO +52 -1
- {devin_cli-1.4.0 → devin_cli-1.5.1}/README.md +51 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/pyproject.toml +1 -1
- devin_cli-1.5.1/src/devin_cli/__init__.py +1 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/client.py +6 -9
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/cli.py +139 -35
- devin_cli-1.5.1/src/devin_cli/config.py +270 -0
- devin_cli-1.5.1/tests/conftest.py +15 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/tests/test_cli.py +8 -0
- devin_cli-1.5.1/tests/test_cli_global_flags.py +203 -0
- devin_cli-1.5.1/tests/test_config.py +194 -0
- devin_cli-1.5.1/tests/test_configure.py +162 -0
- devin_cli-1.4.0/src/devin_cli/__init__.py +0 -1
- devin_cli-1.4.0/src/devin_cli/config.py +0 -153
- devin_cli-1.4.0/tests/conftest.py +0 -8
- devin_cli-1.4.0/tests/test_config.py +0 -27
- {devin_cli-1.4.0 → devin_cli-1.5.1}/.github/workflows/pypi-publish.yml +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/LICENSE +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/assets/logo.png +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/generate_facades.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/setup.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/__init__.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/attachments.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/audit_logs.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/blueprints.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/code_scans.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/consumption.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/guardrail_violations.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/ip_access_list.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/knowledge.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/members.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/organizations.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/playbooks.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/pr_reviews.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/queue.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/repositories.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/schedules.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/secrets.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/service_users.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/sessions.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/tags.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v1/__init__.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v1/attachments.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v1/knowledge.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v1/playbooks.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v1/secrets.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v1/sessions.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/__init__.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/attachments.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/audit_logs.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/blueprints.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/code_scans.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/consumption.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/guardrail_violations.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/ip_access_list.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/knowledge.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/members.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/organizations.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/playbooks.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/pr_reviews.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/queue.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/repositories.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/schedules.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/secrets.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/service_users.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/sessions.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/src/devin_cli/api/v3/tags.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/tests/__init__.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/tests/test_api/test_attachments.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/tests/test_api/test_enterprise_endpoints.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/tests/test_api/test_knowledge.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/tests/test_api/test_playbooks.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/tests/test_api/test_pr_reviews.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/tests/test_api/test_repositories.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/tests/test_api/test_schedules.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/tests/test_api/test_secrets.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/tests/test_api/test_sessions.py +0 -0
- {devin_cli-1.4.0 → devin_cli-1.5.1}/tests/test_cli_extended.py +0 -0
|
@@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.5.1] - 2026-09-04
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Thread Context Propagation for Concurrent Commands (`repos status`, `sessions cost`)**:
|
|
11
|
+
- Resolved an issue where commands utilizing `concurrent.futures.ThreadPoolExecutor` lost runtime credentials (`--token`, `--org`) in worker threads, causing `repos status` to emit fake 404 `"Not registered in Devin (404)"` errors when relying solely on global flags without environment variables.
|
|
12
|
+
- Ensured `contextvars.copy_context().run` is executed per worker task and backed by instance-level fallback in `Config` to guarantee thread-safe credential access across any thread pool.
|
|
13
|
+
- **Root Callback Runtime Credential Re-application**:
|
|
14
|
+
- Re-applied parsed flags and supported equals syntax (`--token=...`, `--org=...`) and flag aliases (`-t`, `-o`, `-b`, `-c`) so commands execute with correct credentials regardless of CLI argument order.
|
|
15
|
+
- **Non-Interactive `configure` Wizard Bypass**:
|
|
16
|
+
- Completely eliminated interactive prompts in `devin configure` when `--token` is provided or in CI environments (`CI`, `GITHUB_ACTIONS`, `CONTINUOUS_INTEGRATION`, `TF_BUILD`), falling back cleanly to defaults (`base_url: https://api.devin.ai/v3`, `api_version: v3`) without requiring `--yes`.
|
|
17
|
+
- Added graceful handling of `EOFError` / `KeyboardInterrupt` to prevent unhandled tracebacks.
|
|
18
|
+
|
|
19
|
+
## [1.5.0] - 2026-09-04
|
|
20
|
+
### Added
|
|
21
|
+
- **On-the-Fly Configuration & Global CLI Flags**: Root command now supports `--token`, `--org`, `--base-url`, `--api-version`, and `--config-file` flags, allowing complete credential configuration on the fly without writing to disk or modifying saved configs.
|
|
22
|
+
- **Predictable Precedence Resolution**: Formalized resolution order across all authentication paths:
|
|
23
|
+
1. CLI Flags (`--token`, `--org`, `--base-url`, `--api-version`)
|
|
24
|
+
2. Environment Variables (`DEVIN_API_TOKEN`, `DEVIN_ORG_ID`, `DEVIN_BASE_URL`, `DEVIN_API_VERSION`)
|
|
25
|
+
3. Config File Profile (`DEVIN_CONFIG_FILE` or `~/.config/devin/config.json`)
|
|
26
|
+
4. Defaults (`base_url: https://api.devin.ai/v3`, `api_version: v3`)
|
|
27
|
+
- **Zero Disk Writes / Deferred File Creation**: Initializing `Config` or invoking commands with flags/environment variables never touches the filesystem. Config file/directory creation is deferred strictly to explicit save actions (such as `devin configure`), fully unblocking read-only and ephemeral CI runners.
|
|
28
|
+
- **Non-Interactive `configure` Command**: Added `--yes` / `-y` flag and non-TTY stdin autodetection to `devin configure`. Omitting options in non-interactive mode falls back to default values and credentials instead of aborting.
|
|
29
|
+
- **Dynamic Config File Override**: Full support for `DEVIN_CONFIG_FILE` environment variable and `--config-file` flag to direct the CLI to custom config files.
|
|
30
|
+
- **Thread/Context-Safe Runtime Overrides**: Runtime overrides are stored via `contextvars.ContextVar` and cleanly scoped per invocation with `config.reset_runtime()`.
|
|
31
|
+
- **Pre-flight Validation & Scoping Hints**: `validate_for_api()` validates credentials before network requests and outputs helpful diagnostic hints (`Org ID: ...`) when endpoints require organization scoping.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- **Sanitized Token and Org Inputs**: Automatic trimming of leading/trailing whitespace and newlines from tokens and organization IDs.
|
|
35
|
+
- **Configure Prompt Bugs**: Fixed an issue where `devin configure` always prompted for API version even when non-interactive, and improved option handling so explicit flags bypass interactive prompts.
|
|
36
|
+
|
|
8
37
|
## [1.4.0] - 2026-08-01
|
|
9
38
|
### Added
|
|
10
39
|
- **Model Selection & Agent Modes**: Full support for `--devin-mode` / `--mode` / `--model` (`normal`, `fast`, `lite`, `ultra`, `fusion`), `--platform`, `--resumable / --no-resumable`, and `--structured-output-required` on `devin sessions create` and `devin create-session`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devin-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.1
|
|
4
4
|
Summary: Unofficial CLI for Devin AI - The first AI Software Engineer
|
|
5
5
|
Project-URL: Homepage, https://github.com/revanthpobala/devin-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/revanthpobala/devin-cli.git
|
|
@@ -75,9 +75,13 @@ pip install devin-cli
|
|
|
75
75
|
|
|
76
76
|
### 2. Configuration
|
|
77
77
|
```bash
|
|
78
|
+
# Interactive setup:
|
|
78
79
|
devin configure
|
|
79
80
|
# Paste your API token (apk_... or cog_...) from https://preview.devin.ai/settings
|
|
80
81
|
# Select API version: v3 (default) or v1 (legacy)
|
|
82
|
+
|
|
83
|
+
# Or non-interactive / CI setup:
|
|
84
|
+
devin configure --token "$DEVIN_API_TOKEN" --org "$DEVIN_ORG_ID" --yes
|
|
81
85
|
```
|
|
82
86
|
|
|
83
87
|
### 3. Your First Session
|
|
@@ -108,6 +112,53 @@ devin --profile personal sessions create "Fix the failing tests"
|
|
|
108
112
|
|
|
109
113
|
Profiles are stored in `~/.config/devin/config.json` — fully isolated including session caches and active session IDs.
|
|
110
114
|
|
|
115
|
+
### Automation & CI / CD
|
|
116
|
+
|
|
117
|
+
`devin-cli` is fully optimized for headless CI/CD runners (GitHub Actions, GitLab CI, Jenkins) and containerized workflows.
|
|
118
|
+
|
|
119
|
+
#### Precedence Resolution
|
|
120
|
+
Credentials and configuration resolve in a strict, predictable order:
|
|
121
|
+
1. **Global CLI Flags** (`--token`, `--org`, `--base-url`, `--api-version`)
|
|
122
|
+
2. **Environment Variables** (`DEVIN_API_TOKEN`, `DEVIN_ORG_ID`, `DEVIN_BASE_URL`, `DEVIN_API_VERSION`)
|
|
123
|
+
3. **Config File** (`DEVIN_CONFIG_FILE` or `~/.config/devin/config.json`)
|
|
124
|
+
4. **Built-in Defaults** (`base_url: https://api.devin.ai/v3`, `api_version: v3`)
|
|
125
|
+
|
|
126
|
+
#### Ephemeral Runs (Zero Disk Writes)
|
|
127
|
+
In CI pipelines, you do not need to create or write configuration files. Pass credentials via environment variables or root CLI flags:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Option A: Environment variables
|
|
131
|
+
export DEVIN_API_TOKEN="cog_..."
|
|
132
|
+
export DEVIN_ORG_ID="org-..."
|
|
133
|
+
devin repos list --json
|
|
134
|
+
|
|
135
|
+
# Option B: Global CLI flags on-the-fly
|
|
136
|
+
devin --token "$DEVIN_API_TOKEN" --org "$DEVIN_ORG_ID" repos status my-org/my-repo --json
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### Non-Interactive Configuration
|
|
140
|
+
If your pipeline writes a config file, use `--yes` (or run in non-TTY environments) to disable interactive prompts:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
devin configure \
|
|
144
|
+
--token "$DEVIN_API_TOKEN" \
|
|
145
|
+
--org "$DEVIN_ORG_ID" \
|
|
146
|
+
--base-url https://api.devin.ai/v3 \
|
|
147
|
+
--api-version v3 \
|
|
148
|
+
--yes
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Custom Config File Path
|
|
152
|
+
Override the default `~/.config/devin/config.json` location:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Via flag
|
|
156
|
+
devin --config-file /tmp/devin-ci.json repos list
|
|
157
|
+
|
|
158
|
+
# Via environment variable
|
|
159
|
+
export DEVIN_CONFIG_FILE=/tmp/devin-ci.json
|
|
160
|
+
```
|
|
161
|
+
|
|
111
162
|
---
|
|
112
163
|
|
|
113
164
|
## 🤖 All Commands
|
|
@@ -39,9 +39,13 @@ pip install devin-cli
|
|
|
39
39
|
|
|
40
40
|
### 2. Configuration
|
|
41
41
|
```bash
|
|
42
|
+
# Interactive setup:
|
|
42
43
|
devin configure
|
|
43
44
|
# Paste your API token (apk_... or cog_...) from https://preview.devin.ai/settings
|
|
44
45
|
# Select API version: v3 (default) or v1 (legacy)
|
|
46
|
+
|
|
47
|
+
# Or non-interactive / CI setup:
|
|
48
|
+
devin configure --token "$DEVIN_API_TOKEN" --org "$DEVIN_ORG_ID" --yes
|
|
45
49
|
```
|
|
46
50
|
|
|
47
51
|
### 3. Your First Session
|
|
@@ -72,6 +76,53 @@ devin --profile personal sessions create "Fix the failing tests"
|
|
|
72
76
|
|
|
73
77
|
Profiles are stored in `~/.config/devin/config.json` — fully isolated including session caches and active session IDs.
|
|
74
78
|
|
|
79
|
+
### Automation & CI / CD
|
|
80
|
+
|
|
81
|
+
`devin-cli` is fully optimized for headless CI/CD runners (GitHub Actions, GitLab CI, Jenkins) and containerized workflows.
|
|
82
|
+
|
|
83
|
+
#### Precedence Resolution
|
|
84
|
+
Credentials and configuration resolve in a strict, predictable order:
|
|
85
|
+
1. **Global CLI Flags** (`--token`, `--org`, `--base-url`, `--api-version`)
|
|
86
|
+
2. **Environment Variables** (`DEVIN_API_TOKEN`, `DEVIN_ORG_ID`, `DEVIN_BASE_URL`, `DEVIN_API_VERSION`)
|
|
87
|
+
3. **Config File** (`DEVIN_CONFIG_FILE` or `~/.config/devin/config.json`)
|
|
88
|
+
4. **Built-in Defaults** (`base_url: https://api.devin.ai/v3`, `api_version: v3`)
|
|
89
|
+
|
|
90
|
+
#### Ephemeral Runs (Zero Disk Writes)
|
|
91
|
+
In CI pipelines, you do not need to create or write configuration files. Pass credentials via environment variables or root CLI flags:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Option A: Environment variables
|
|
95
|
+
export DEVIN_API_TOKEN="cog_..."
|
|
96
|
+
export DEVIN_ORG_ID="org-..."
|
|
97
|
+
devin repos list --json
|
|
98
|
+
|
|
99
|
+
# Option B: Global CLI flags on-the-fly
|
|
100
|
+
devin --token "$DEVIN_API_TOKEN" --org "$DEVIN_ORG_ID" repos status my-org/my-repo --json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Non-Interactive Configuration
|
|
104
|
+
If your pipeline writes a config file, use `--yes` (or run in non-TTY environments) to disable interactive prompts:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
devin configure \
|
|
108
|
+
--token "$DEVIN_API_TOKEN" \
|
|
109
|
+
--org "$DEVIN_ORG_ID" \
|
|
110
|
+
--base-url https://api.devin.ai/v3 \
|
|
111
|
+
--api-version v3 \
|
|
112
|
+
--yes
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### Custom Config File Path
|
|
116
|
+
Override the default `~/.config/devin/config.json` location:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Via flag
|
|
120
|
+
devin --config-file /tmp/devin-ci.json repos list
|
|
121
|
+
|
|
122
|
+
# Via environment variable
|
|
123
|
+
export DEVIN_CONFIG_FILE=/tmp/devin-ci.json
|
|
124
|
+
```
|
|
125
|
+
|
|
75
126
|
---
|
|
76
127
|
|
|
77
128
|
## 🤖 All Commands
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.5.1"
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import httpx
|
|
2
|
-
from devin_cli.config import config
|
|
2
|
+
from devin_cli.config import config, APIError
|
|
3
3
|
from rich.console import Console
|
|
4
4
|
import sys
|
|
5
5
|
from typing import Optional, Any, Dict
|
|
6
6
|
|
|
7
7
|
console = Console()
|
|
8
8
|
|
|
9
|
-
class APIError(Exception):
|
|
10
|
-
def __init__(self, message: str, status_code: Optional[int] = None):
|
|
11
|
-
super().__init__(message)
|
|
12
|
-
self.status_code = status_code
|
|
13
|
-
|
|
14
9
|
class BaseClient:
|
|
15
10
|
def __init__(self):
|
|
16
11
|
pass
|
|
@@ -34,9 +29,7 @@ class BaseClient:
|
|
|
34
29
|
return config.base_url.rstrip("/")
|
|
35
30
|
|
|
36
31
|
def _ensure_token(self):
|
|
37
|
-
|
|
38
|
-
# Only raise error if meaningful operation is attempted
|
|
39
|
-
raise APIError("API token not found. Run 'devin configure' to set your API token.")
|
|
32
|
+
config.validate_for_api()
|
|
40
33
|
|
|
41
34
|
def _handle_response(self, response: httpx.Response) -> Any:
|
|
42
35
|
try:
|
|
@@ -53,6 +46,10 @@ class BaseClient:
|
|
|
53
46
|
error_detail = e.response.text.strip()
|
|
54
47
|
|
|
55
48
|
msg_suffix = f"\n Path: {path}"
|
|
49
|
+
if config.org_id:
|
|
50
|
+
msg_suffix += f"\n Org ID: {config.org_id}"
|
|
51
|
+
elif "/organizations/" not in path and not path.startswith("/enterprise"):
|
|
52
|
+
msg_suffix += f"\n Org ID: None (Hint: this endpoint may require --org or DEVIN_ORG_ID)"
|
|
56
53
|
if error_detail:
|
|
57
54
|
msg_suffix += f"\n Detail: {error_detail}"
|
|
58
55
|
|
|
@@ -36,19 +36,50 @@ import importlib.metadata
|
|
|
36
36
|
from rich.prompt import Prompt
|
|
37
37
|
|
|
38
38
|
try:
|
|
39
|
-
|
|
40
|
-
except
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
from devin_cli import __version__
|
|
40
|
+
except Exception:
|
|
41
|
+
try:
|
|
42
|
+
__version__ = importlib.metadata.version("devin-cli")
|
|
43
|
+
except importlib.metadata.PackageNotFoundError:
|
|
44
|
+
__version__ = "1.5.0"
|
|
45
|
+
|
|
46
|
+
# Early CLI flag detection for accurate menus, config loading, and mode setup
|
|
47
|
+
def _extract_cli_arg(flags: list) -> Optional[str]:
|
|
48
|
+
for flag in flags:
|
|
49
|
+
for idx, arg in enumerate(sys.argv):
|
|
50
|
+
if arg == flag:
|
|
51
|
+
if idx + 1 < len(sys.argv) and not sys.argv[idx + 1].startswith("-"):
|
|
52
|
+
return sys.argv[idx + 1]
|
|
53
|
+
elif arg.startswith(f"{flag}="):
|
|
54
|
+
return arg.split("=", 1)[1]
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
early_config_file = _extract_cli_arg(["--config-file", "-c"])
|
|
58
|
+
if early_config_file:
|
|
59
|
+
config.set_config_file(early_config_file)
|
|
60
|
+
elif os.environ.get("DEVIN_CONFIG_FILE"):
|
|
61
|
+
config.set_config_file(os.environ["DEVIN_CONFIG_FILE"])
|
|
62
|
+
|
|
63
|
+
early_profile = _extract_cli_arg(["--profile", "-p"])
|
|
64
|
+
if early_profile:
|
|
65
|
+
config.active_profile = early_profile
|
|
66
|
+
|
|
67
|
+
early_token = _extract_cli_arg(["--token", "-t"])
|
|
68
|
+
if early_token:
|
|
69
|
+
config.runtime.api_token = early_token
|
|
70
|
+
|
|
71
|
+
early_org = _extract_cli_arg(["--org", "-o"])
|
|
72
|
+
if early_org:
|
|
73
|
+
config.runtime.org_id = early_org
|
|
74
|
+
config.temporary_org_id = early_org
|
|
75
|
+
|
|
76
|
+
early_base_url = _extract_cli_arg(["--base-url", "-b"])
|
|
77
|
+
if early_base_url:
|
|
78
|
+
config.runtime.base_url = early_base_url
|
|
79
|
+
|
|
80
|
+
early_api_version = _extract_cli_arg(["--api-version"])
|
|
81
|
+
if early_api_version:
|
|
82
|
+
config.runtime.api_version = early_api_version
|
|
52
83
|
|
|
53
84
|
IS_V1 = config.api_version == "v1"
|
|
54
85
|
v1_tag = " [bold yellow](Legacy v1 API)[/bold yellow]" if IS_V1 else ""
|
|
@@ -101,12 +132,25 @@ ASCII_LOGO = r"""
|
|
|
101
132
|
def main(
|
|
102
133
|
ctx: typer.Context,
|
|
103
134
|
profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Configuration profile to use (e.g., service, personal)"),
|
|
135
|
+
token: Optional[str] = typer.Option(None, "--token", help="Devin API token (starts with apk_ or cog_). Fallback: DEVIN_API_TOKEN"),
|
|
136
|
+
org: Optional[str] = typer.Option(None, "--org", help="Organization ID (org-...). Fallback: DEVIN_ORG_ID"),
|
|
137
|
+
base_url: Optional[str] = typer.Option(None, "--base-url", help="Devin API base URL (default: https://api.devin.ai/v3). Fallback: DEVIN_BASE_URL"),
|
|
138
|
+
api_version: Optional[str] = typer.Option(None, "--api-version", help="Devin API version: v3 or v1 (default: v3). Fallback: DEVIN_API_VERSION"),
|
|
139
|
+
config_file: Optional[Path] = typer.Option(None, "--config-file", help="Custom path to config file. Fallback: DEVIN_CONFIG_FILE"),
|
|
104
140
|
version: Optional[bool] = typer.Option(None, "--version", "-v", help="Show the application's version and exit."),
|
|
105
141
|
json_format: bool = typer.Option(False, "--json", help="Output raw JSON (for AI agents & scripting)"),
|
|
106
142
|
):
|
|
107
143
|
"""
|
|
108
144
|
Unofficial CLI for Devin AI v3.
|
|
109
145
|
"""
|
|
146
|
+
config.reset_runtime()
|
|
147
|
+
|
|
148
|
+
eff_token = token or _extract_cli_arg(["--token", "-t"])
|
|
149
|
+
eff_org = org or _extract_cli_arg(["--org", "-o"])
|
|
150
|
+
eff_base_url = base_url or _extract_cli_arg(["--base-url", "-b"])
|
|
151
|
+
eff_api_version = api_version or _extract_cli_arg(["--api-version"])
|
|
152
|
+
eff_config_file = config_file or _extract_cli_arg(["--config-file", "-c"])
|
|
153
|
+
|
|
110
154
|
if json_format:
|
|
111
155
|
os.environ["DEVIN_OUTPUT_FORMAT"] = "json"
|
|
112
156
|
|
|
@@ -114,8 +158,20 @@ def main(
|
|
|
114
158
|
console.print(f"devin CLI version: {__version__}")
|
|
115
159
|
raise typer.Exit()
|
|
116
160
|
|
|
161
|
+
if eff_config_file:
|
|
162
|
+
config.set_config_file(eff_config_file)
|
|
117
163
|
if profile:
|
|
118
164
|
config.active_profile = profile
|
|
165
|
+
if eff_token:
|
|
166
|
+
config.runtime.api_token = eff_token
|
|
167
|
+
if eff_org:
|
|
168
|
+
config.runtime.org_id = eff_org
|
|
169
|
+
config.temporary_org_id = eff_org
|
|
170
|
+
if eff_base_url:
|
|
171
|
+
config.runtime.base_url = eff_base_url
|
|
172
|
+
if eff_api_version:
|
|
173
|
+
config.runtime.api_version = eff_api_version
|
|
174
|
+
|
|
119
175
|
if ctx.invoked_subcommand is None:
|
|
120
176
|
console.print(ASCII_LOGO)
|
|
121
177
|
|
|
@@ -150,10 +206,10 @@ def handle_api_error(func):
|
|
|
150
206
|
console.print(f"[bold red]Connection Error:[/bold red] Failed to connect to {config.base_url}")
|
|
151
207
|
console.print(f"[dim]{str(e)}[/dim]")
|
|
152
208
|
raise typer.Exit(1)
|
|
153
|
-
elif hasattr(e, "response"): # Standard APIError handling
|
|
209
|
+
elif isinstance(e, APIError) or hasattr(e, "response"): # Standard APIError handling
|
|
154
210
|
if is_json:
|
|
155
211
|
try:
|
|
156
|
-
err_json = e.response.json()
|
|
212
|
+
err_json = e.response.json() if hasattr(e, "response") else {"error": str(e)}
|
|
157
213
|
except Exception:
|
|
158
214
|
err_json = {"error": str(e)}
|
|
159
215
|
print(json.dumps(err_json, indent=2))
|
|
@@ -178,10 +234,12 @@ def get_current_session_id():
|
|
|
178
234
|
|
|
179
235
|
@app.command()
|
|
180
236
|
def configure(
|
|
181
|
-
token: str = typer.Option(
|
|
182
|
-
base_url: str = typer.Option("
|
|
183
|
-
org_id: Optional[str] = typer.Option(None, "--org",
|
|
237
|
+
token: Optional[str] = typer.Option(None, "--token", "-t", help="Your Devin API Token (starts with apk_ or cog_)"),
|
|
238
|
+
base_url: Optional[str] = typer.Option(None, "--base-url", "-b", help="Devin API Base URL (default: https://api.devin.ai/v3)"),
|
|
239
|
+
org_id: Optional[str] = typer.Option(None, "--org", help="Default Organization ID"),
|
|
240
|
+
api_version: Optional[str] = typer.Option(None, "--api-version", help="API Version (v3 or v1)"),
|
|
184
241
|
profile: Optional[str] = typer.Option(None, "--profile", help="Profile to configure (overrides global --profile)"),
|
|
242
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Non-interactive mode (never prompt for input)"),
|
|
185
243
|
):
|
|
186
244
|
"""
|
|
187
245
|
Configure the CLI with your Devin API token and organization.
|
|
@@ -189,21 +247,63 @@ def configure(
|
|
|
189
247
|
if profile:
|
|
190
248
|
config.active_profile = profile
|
|
191
249
|
|
|
192
|
-
|
|
250
|
+
is_ci = bool(
|
|
251
|
+
os.environ.get("CI")
|
|
252
|
+
or os.environ.get("GITHUB_ACTIONS")
|
|
253
|
+
or os.environ.get("CONTINUOUS_INTEGRATION")
|
|
254
|
+
or os.environ.get("TF_BUILD")
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Check if token was provided via argument, early CLI arg, or runtime
|
|
258
|
+
token_val = token or _extract_cli_arg(["--token", "-t"]) or config.runtime.api_token
|
|
259
|
+
|
|
260
|
+
# Prompt interactively ONLY when no token was provided, --yes is not passed, not in CI, and stdin is a TTY
|
|
261
|
+
is_interactive = not yes and not is_ci and sys.stdin.isatty() and token_val is None
|
|
262
|
+
|
|
263
|
+
if not is_interactive:
|
|
264
|
+
final_token = token_val or config.api_token
|
|
265
|
+
if not final_token:
|
|
266
|
+
console.print("[bold red]Error:[/bold red] API token is required. Pass --token or set DEVIN_API_TOKEN.")
|
|
267
|
+
raise typer.Exit(1)
|
|
268
|
+
final_base_url = base_url or _extract_cli_arg(["--base-url", "-b"]) or config.base_url or "https://api.devin.ai/v3"
|
|
269
|
+
final_api_version = (api_version or _extract_cli_arg(["--api-version"]) or config.api_version or "v3").lower()
|
|
270
|
+
final_org = org_id if org_id is not None else (_extract_cli_arg(["--org"]) or config.org_id)
|
|
271
|
+
else:
|
|
272
|
+
try:
|
|
273
|
+
final_token = Prompt.ask("Devin API Token (starts with apk_ or cog_)")
|
|
274
|
+
if not final_token:
|
|
275
|
+
console.print("[bold red]Error:[/bold red] Token cannot be empty.")
|
|
276
|
+
raise typer.Exit(1)
|
|
277
|
+
|
|
278
|
+
final_base_url = base_url or Prompt.ask("Devin API Base URL", default=config.base_url or "https://api.devin.ai/v3")
|
|
279
|
+
|
|
280
|
+
if org_id is not None:
|
|
281
|
+
final_org = org_id
|
|
282
|
+
else:
|
|
283
|
+
org_prompt = Prompt.ask("Organization ID (optional)", default=config.org_id or "")
|
|
284
|
+
final_org = org_prompt if org_prompt else None
|
|
285
|
+
|
|
286
|
+
if api_version is not None:
|
|
287
|
+
final_api_version = api_version.lower()
|
|
288
|
+
else:
|
|
289
|
+
final_api_version = Prompt.ask(f"API Version for profile '{config.active_profile}' (v3/v1)", default=config.api_version or "v3").lower()
|
|
290
|
+
except (EOFError, KeyboardInterrupt):
|
|
291
|
+
console.print("[bold red]Aborted.[/bold red]")
|
|
292
|
+
raise typer.Exit(1)
|
|
293
|
+
|
|
294
|
+
if not (final_token.startswith("apk_") or final_token.startswith("cog_")):
|
|
193
295
|
console.print("[bold yellow]Warning:[/bold yellow] Token format might be outdated. v3 tokens usually start with 'apk_' or 'cog_'.")
|
|
194
296
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
config.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if org_id:
|
|
206
|
-
config.org_id = org_id
|
|
297
|
+
if "api.devin.ai" in final_base_url and "v3" in final_base_url and final_api_version == "v1":
|
|
298
|
+
final_base_url = final_base_url.replace("/v3", "/v1")
|
|
299
|
+
elif "api.devin.ai" in final_base_url and "v1" in final_base_url and final_api_version == "v3":
|
|
300
|
+
final_base_url = final_base_url.replace("/v1", "/v3")
|
|
301
|
+
|
|
302
|
+
config.api_token = final_token
|
|
303
|
+
config.base_url = final_base_url
|
|
304
|
+
config.api_version = final_api_version
|
|
305
|
+
if final_org:
|
|
306
|
+
config.org_id = final_org
|
|
207
307
|
console.print(f"[green]Configuration saved to {config.config_file}[/green]")
|
|
208
308
|
return None
|
|
209
309
|
|
|
@@ -532,9 +632,10 @@ def session_cost_cmd(
|
|
|
532
632
|
except Exception:
|
|
533
633
|
return None
|
|
534
634
|
|
|
635
|
+
import contextvars
|
|
535
636
|
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
|
|
536
|
-
f_session = executor.submit(fetch_session)
|
|
537
|
-
f_cons = executor.submit(fetch_consumption)
|
|
637
|
+
f_session = executor.submit(contextvars.copy_context().run, fetch_session)
|
|
638
|
+
f_cons = executor.submit(contextvars.copy_context().run, fetch_consumption)
|
|
538
639
|
|
|
539
640
|
resp = f_session.result()
|
|
540
641
|
cons_resp = f_cons.result()
|
|
@@ -1161,12 +1262,15 @@ def status_repo_cmd(
|
|
|
1161
1262
|
json_output: bool = typer.Option(False, "--json"),
|
|
1162
1263
|
):
|
|
1163
1264
|
"""Get indexing status for one or more repositories."""
|
|
1164
|
-
if org:
|
|
1265
|
+
if org:
|
|
1266
|
+
config.temporary_org_id = org
|
|
1267
|
+
config.runtime.org_id = org
|
|
1165
1268
|
|
|
1166
1269
|
results = []
|
|
1167
1270
|
exit_code = 0
|
|
1168
1271
|
|
|
1169
1272
|
from concurrent.futures import ThreadPoolExecutor
|
|
1273
|
+
import contextvars
|
|
1170
1274
|
|
|
1171
1275
|
def fetch_status(path):
|
|
1172
1276
|
try:
|
|
@@ -1189,7 +1293,7 @@ def status_repo_cmd(
|
|
|
1189
1293
|
|
|
1190
1294
|
max_workers = min(10, len(paths))
|
|
1191
1295
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
1192
|
-
futures = {executor.submit(fetch_status, path): path for path in paths}
|
|
1296
|
+
futures = {executor.submit(contextvars.copy_context().run, fetch_status, path): path for path in paths}
|
|
1193
1297
|
for future in futures:
|
|
1194
1298
|
path = futures[future]
|
|
1195
1299
|
try:
|