devin-cli 1.4.0__tar.gz → 1.5.0__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.
Files changed (80) hide show
  1. {devin_cli-1.4.0 → devin_cli-1.5.0}/.gitignore +1 -0
  2. {devin_cli-1.4.0 → devin_cli-1.5.0}/CHANGELOG.md +18 -0
  3. {devin_cli-1.4.0 → devin_cli-1.5.0}/PKG-INFO +52 -1
  4. {devin_cli-1.4.0 → devin_cli-1.5.0}/README.md +51 -0
  5. {devin_cli-1.4.0 → devin_cli-1.5.0}/pyproject.toml +1 -1
  6. devin_cli-1.5.0/src/devin_cli/__init__.py +1 -0
  7. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/client.py +6 -9
  8. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/cli.py +111 -31
  9. devin_cli-1.5.0/src/devin_cli/config.py +260 -0
  10. devin_cli-1.5.0/tests/conftest.py +15 -0
  11. {devin_cli-1.4.0 → devin_cli-1.5.0}/tests/test_cli.py +7 -0
  12. devin_cli-1.5.0/tests/test_cli_global_flags.py +112 -0
  13. devin_cli-1.5.0/tests/test_config.py +194 -0
  14. devin_cli-1.5.0/tests/test_configure.py +102 -0
  15. devin_cli-1.4.0/src/devin_cli/__init__.py +0 -1
  16. devin_cli-1.4.0/src/devin_cli/config.py +0 -153
  17. devin_cli-1.4.0/tests/conftest.py +0 -8
  18. devin_cli-1.4.0/tests/test_config.py +0 -27
  19. {devin_cli-1.4.0 → devin_cli-1.5.0}/.github/workflows/pypi-publish.yml +0 -0
  20. {devin_cli-1.4.0 → devin_cli-1.5.0}/LICENSE +0 -0
  21. {devin_cli-1.4.0 → devin_cli-1.5.0}/assets/logo.png +0 -0
  22. {devin_cli-1.4.0 → devin_cli-1.5.0}/generate_facades.py +0 -0
  23. {devin_cli-1.4.0 → devin_cli-1.5.0}/setup.py +0 -0
  24. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/__init__.py +0 -0
  25. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/attachments.py +0 -0
  26. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/audit_logs.py +0 -0
  27. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/blueprints.py +0 -0
  28. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/code_scans.py +0 -0
  29. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/consumption.py +0 -0
  30. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/guardrail_violations.py +0 -0
  31. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/ip_access_list.py +0 -0
  32. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/knowledge.py +0 -0
  33. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/members.py +0 -0
  34. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/organizations.py +0 -0
  35. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/playbooks.py +0 -0
  36. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/pr_reviews.py +0 -0
  37. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/queue.py +0 -0
  38. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/repositories.py +0 -0
  39. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/schedules.py +0 -0
  40. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/secrets.py +0 -0
  41. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/service_users.py +0 -0
  42. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/sessions.py +0 -0
  43. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/tags.py +0 -0
  44. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v1/__init__.py +0 -0
  45. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v1/attachments.py +0 -0
  46. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v1/knowledge.py +0 -0
  47. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v1/playbooks.py +0 -0
  48. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v1/secrets.py +0 -0
  49. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v1/sessions.py +0 -0
  50. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/__init__.py +0 -0
  51. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/attachments.py +0 -0
  52. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/audit_logs.py +0 -0
  53. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/blueprints.py +0 -0
  54. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/code_scans.py +0 -0
  55. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/consumption.py +0 -0
  56. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/guardrail_violations.py +0 -0
  57. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/ip_access_list.py +0 -0
  58. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/knowledge.py +0 -0
  59. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/members.py +0 -0
  60. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/organizations.py +0 -0
  61. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/playbooks.py +0 -0
  62. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/pr_reviews.py +0 -0
  63. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/queue.py +0 -0
  64. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/repositories.py +0 -0
  65. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/schedules.py +0 -0
  66. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/secrets.py +0 -0
  67. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/service_users.py +0 -0
  68. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/sessions.py +0 -0
  69. {devin_cli-1.4.0 → devin_cli-1.5.0}/src/devin_cli/api/v3/tags.py +0 -0
  70. {devin_cli-1.4.0 → devin_cli-1.5.0}/tests/__init__.py +0 -0
  71. {devin_cli-1.4.0 → devin_cli-1.5.0}/tests/test_api/test_attachments.py +0 -0
  72. {devin_cli-1.4.0 → devin_cli-1.5.0}/tests/test_api/test_enterprise_endpoints.py +0 -0
  73. {devin_cli-1.4.0 → devin_cli-1.5.0}/tests/test_api/test_knowledge.py +0 -0
  74. {devin_cli-1.4.0 → devin_cli-1.5.0}/tests/test_api/test_playbooks.py +0 -0
  75. {devin_cli-1.4.0 → devin_cli-1.5.0}/tests/test_api/test_pr_reviews.py +0 -0
  76. {devin_cli-1.4.0 → devin_cli-1.5.0}/tests/test_api/test_repositories.py +0 -0
  77. {devin_cli-1.4.0 → devin_cli-1.5.0}/tests/test_api/test_schedules.py +0 -0
  78. {devin_cli-1.4.0 → devin_cli-1.5.0}/tests/test_api/test_secrets.py +0 -0
  79. {devin_cli-1.4.0 → devin_cli-1.5.0}/tests/test_api/test_sessions.py +0 -0
  80. {devin_cli-1.4.0 → devin_cli-1.5.0}/tests/test_cli_extended.py +0 -0
@@ -23,6 +23,7 @@ wheels/
23
23
 
24
24
  # Virtual Environment
25
25
  venv/
26
+ .venv/
26
27
  ENV/
27
28
 
28
29
  # Testing
@@ -5,6 +5,24 @@ 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.0] - 2026-09-04
9
+ ### Added
10
+ - **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.
11
+ - **Predictable Precedence Resolution**: Formalized resolution order across all authentication paths:
12
+ 1. CLI Flags (`--token`, `--org`, `--base-url`, `--api-version`)
13
+ 2. Environment Variables (`DEVIN_API_TOKEN`, `DEVIN_ORG_ID`, `DEVIN_BASE_URL`, `DEVIN_API_VERSION`)
14
+ 3. Config File Profile (`DEVIN_CONFIG_FILE` or `~/.config/devin/config.json`)
15
+ 4. Defaults (`base_url: https://api.devin.ai/v3`, `api_version: v3`)
16
+ - **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.
17
+ - **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.
18
+ - **Dynamic Config File Override**: Full support for `DEVIN_CONFIG_FILE` environment variable and `--config-file` flag to direct the CLI to custom config files.
19
+ - **Thread/Context-Safe Runtime Overrides**: Runtime overrides are stored via `contextvars.ContextVar` and cleanly scoped per invocation with `config.reset_runtime()`.
20
+ - **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.
21
+
22
+ ### Fixed
23
+ - **Sanitized Token and Org Inputs**: Automatic trimming of leading/trailing whitespace and newlines from tokens and organization IDs.
24
+ - **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.
25
+
8
26
  ## [1.4.0] - 2026-08-01
9
27
  ### Added
10
28
  - **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.4.0
3
+ Version: 1.5.0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devin-cli"
3
- version = "1.4.0"
3
+ version = "1.5.0"
4
4
  description = "Unofficial CLI for Devin AI - The first AI Software Engineer"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -0,0 +1 @@
1
+ __version__ = "1.5.0"
@@ -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
- if not self.token:
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
- __version__ = importlib.metadata.version("devin-cli")
40
- except importlib.metadata.PackageNotFoundError:
41
- __version__ = "unknown"
42
-
43
- # Early profile detection for accurate --help menus
44
- if "--profile" in sys.argv:
45
- idx = sys.argv.index("--profile")
46
- if idx + 1 < len(sys.argv):
47
- config.active_profile = sys.argv[idx + 1]
48
- elif "-p" in sys.argv:
49
- idx = sys.argv.index("-p")
50
- if idx + 1 < len(sys.argv):
51
- config.active_profile = sys.argv[idx + 1]
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
+ if flag in sys.argv:
50
+ try:
51
+ idx = sys.argv.index(flag)
52
+ if idx + 1 < len(sys.argv) and not sys.argv[idx + 1].startswith("-"):
53
+ return sys.argv[idx + 1]
54
+ except ValueError:
55
+ pass
56
+ return None
57
+
58
+ early_config_file = _extract_cli_arg(["--config-file"])
59
+ if early_config_file:
60
+ config.set_config_file(early_config_file)
61
+ elif os.environ.get("DEVIN_CONFIG_FILE"):
62
+ config.set_config_file(os.environ["DEVIN_CONFIG_FILE"])
63
+
64
+ early_profile = _extract_cli_arg(["--profile", "-p"])
65
+ if early_profile:
66
+ config.active_profile = early_profile
67
+
68
+ early_token = _extract_cli_arg(["--token"])
69
+ if early_token:
70
+ config.runtime.api_token = early_token
71
+
72
+ early_org = _extract_cli_arg(["--org"])
73
+ if early_org:
74
+ config.runtime.org_id = early_org
75
+
76
+ early_base_url = _extract_cli_arg(["--base-url"])
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,19 @@ 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
+
110
148
  if json_format:
111
149
  os.environ["DEVIN_OUTPUT_FORMAT"] = "json"
112
150
 
@@ -114,8 +152,19 @@ def main(
114
152
  console.print(f"devin CLI version: {__version__}")
115
153
  raise typer.Exit()
116
154
 
155
+ if config_file:
156
+ config.set_config_file(config_file)
117
157
  if profile:
118
158
  config.active_profile = profile
159
+ if token:
160
+ config.runtime.api_token = token
161
+ if org:
162
+ config.runtime.org_id = org
163
+ if base_url:
164
+ config.runtime.base_url = base_url
165
+ if api_version:
166
+ config.runtime.api_version = api_version
167
+
119
168
  if ctx.invoked_subcommand is None:
120
169
  console.print(ASCII_LOGO)
121
170
 
@@ -150,10 +199,10 @@ def handle_api_error(func):
150
199
  console.print(f"[bold red]Connection Error:[/bold red] Failed to connect to {config.base_url}")
151
200
  console.print(f"[dim]{str(e)}[/dim]")
152
201
  raise typer.Exit(1)
153
- elif hasattr(e, "response"): # Standard APIError handling
202
+ elif isinstance(e, APIError) or hasattr(e, "response"): # Standard APIError handling
154
203
  if is_json:
155
204
  try:
156
- err_json = e.response.json()
205
+ err_json = e.response.json() if hasattr(e, "response") else {"error": str(e)}
157
206
  except Exception:
158
207
  err_json = {"error": str(e)}
159
208
  print(json.dumps(err_json, indent=2))
@@ -178,10 +227,12 @@ def get_current_session_id():
178
227
 
179
228
  @app.command()
180
229
  def configure(
181
- token: str = typer.Option(..., prompt="Devin API Token (starts with apk_ or cog_)", help="Your Devin API Token"),
182
- base_url: str = typer.Option("https://api.devin.ai/v3", prompt="Devin API Base URL", help="Devin API Base URL"),
183
- org_id: Optional[str] = typer.Option(None, "--org", prompt="Organization ID (optional)", help="Default Organization ID"),
230
+ token: Optional[str] = typer.Option(None, "--token", "-t", help="Your Devin API Token (starts with apk_ or cog_)"),
231
+ base_url: Optional[str] = typer.Option(None, "--base-url", "-b", help="Devin API Base URL (default: https://api.devin.ai/v3)"),
232
+ org_id: Optional[str] = typer.Option(None, "--org", help="Default Organization ID"),
233
+ api_version: Optional[str] = typer.Option(None, "--api-version", help="API Version (v3 or v1)"),
184
234
  profile: Optional[str] = typer.Option(None, "--profile", help="Profile to configure (overrides global --profile)"),
235
+ yes: bool = typer.Option(False, "--yes", "-y", help="Non-interactive mode (never prompt for input)"),
185
236
  ):
186
237
  """
187
238
  Configure the CLI with your Devin API token and organization.
@@ -189,21 +240,50 @@ def configure(
189
240
  if profile:
190
241
  config.active_profile = profile
191
242
 
192
- if not (token.startswith("apk_") or token.startswith("cog_")):
243
+ is_interactive = not yes and sys.stdin.isatty()
244
+
245
+ if not is_interactive:
246
+ final_token = token or config.api_token
247
+ if not final_token:
248
+ console.print("[bold red]Error:[/bold red] API token is required. Pass --token or set DEVIN_API_TOKEN.")
249
+ raise typer.Exit(1)
250
+ final_base_url = base_url or config.base_url or "https://api.devin.ai/v3"
251
+ final_api_version = (api_version or config.api_version or "v3").lower()
252
+ final_org = org_id if org_id is not None else config.org_id
253
+ else:
254
+ final_token = token
255
+ if not final_token:
256
+ final_token = Prompt.ask("Devin API Token (starts with apk_ or cog_)")
257
+ if not final_token:
258
+ console.print("[bold red]Error:[/bold red] Token cannot be empty.")
259
+ raise typer.Exit(1)
260
+
261
+ final_base_url = base_url or Prompt.ask("Devin API Base URL", default=config.base_url or "https://api.devin.ai/v3")
262
+
263
+ if org_id is not None:
264
+ final_org = org_id
265
+ else:
266
+ org_prompt = Prompt.ask("Organization ID (optional)", default=config.org_id or "")
267
+ final_org = org_prompt if org_prompt else None
268
+
269
+ if api_version is not None:
270
+ final_api_version = api_version.lower()
271
+ else:
272
+ final_api_version = Prompt.ask(f"API Version for profile '{config.active_profile}' (v3/v1)", default=config.api_version or "v3").lower()
273
+
274
+ if not (final_token.startswith("apk_") or final_token.startswith("cog_")):
193
275
  console.print("[bold yellow]Warning:[/bold yellow] Token format might be outdated. v3 tokens usually start with 'apk_' or 'cog_'.")
194
276
 
195
- api_version_prompt = Prompt.ask(f"API Version for profile '{config.active_profile}' (v3/v1)", default=config.api_version or "v3")
196
-
197
- if "api.devin.ai" in base_url and "v3" in base_url and api_version_prompt == "v1":
198
- base_url = base_url.replace("/v3", "/v1")
199
- elif "api.devin.ai" in base_url and "v1" in base_url and api_version_prompt == "v3":
200
- base_url = base_url.replace("/v1", "/v3")
201
-
202
- config.api_token = token
203
- config.base_url = base_url
204
- config.api_version = api_version_prompt
205
- if org_id:
206
- config.org_id = org_id
277
+ if "api.devin.ai" in final_base_url and "v3" in final_base_url and final_api_version == "v1":
278
+ final_base_url = final_base_url.replace("/v3", "/v1")
279
+ elif "api.devin.ai" in final_base_url and "v1" in final_base_url and final_api_version == "v3":
280
+ final_base_url = final_base_url.replace("/v1", "/v3")
281
+
282
+ config.api_token = final_token
283
+ config.base_url = final_base_url
284
+ config.api_version = final_api_version
285
+ if final_org:
286
+ config.org_id = final_org
207
287
  console.print(f"[green]Configuration saved to {config.config_file}[/green]")
208
288
  return None
209
289