devin-cli 1.2.4__tar.gz → 1.3.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 (56) hide show
  1. {devin_cli-1.2.4 → devin_cli-1.3.0}/PKG-INFO +11 -2
  2. {devin_cli-1.2.4 → devin_cli-1.3.0}/README.md +10 -1
  3. devin_cli-1.3.0/patch_cli.py +78 -0
  4. {devin_cli-1.2.4 → devin_cli-1.3.0}/pyproject.toml +1 -1
  5. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/cli.py +60 -9
  6. devin_cli-1.3.0/test_json.py +46 -0
  7. devin_cli-1.3.0/test_json_wrapper.py +47 -0
  8. {devin_cli-1.2.4 → devin_cli-1.3.0}/tests/test_cli.py +1 -1
  9. {devin_cli-1.2.4 → devin_cli-1.3.0}/.github/workflows/pypi-publish.yml +0 -0
  10. {devin_cli-1.2.4 → devin_cli-1.3.0}/.gitignore +0 -0
  11. {devin_cli-1.2.4 → devin_cli-1.3.0}/CHANGELOG.md +0 -0
  12. {devin_cli-1.2.4 → devin_cli-1.3.0}/LICENSE +0 -0
  13. {devin_cli-1.2.4 → devin_cli-1.3.0}/assets/logo.png +0 -0
  14. {devin_cli-1.2.4 → devin_cli-1.3.0}/generate_facades.py +0 -0
  15. {devin_cli-1.2.4 → devin_cli-1.3.0}/setup.py +0 -0
  16. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/__init__.py +0 -0
  17. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/__init__.py +0 -0
  18. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/attachments.py +0 -0
  19. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/client.py +0 -0
  20. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/consumption.py +0 -0
  21. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/knowledge.py +0 -0
  22. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/members.py +0 -0
  23. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/organizations.py +0 -0
  24. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/playbooks.py +0 -0
  25. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/repositories.py +0 -0
  26. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/schedules.py +0 -0
  27. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/secrets.py +0 -0
  28. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/sessions.py +0 -0
  29. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v1/__init__.py +0 -0
  30. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v1/attachments.py +0 -0
  31. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v1/knowledge.py +0 -0
  32. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v1/playbooks.py +0 -0
  33. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v1/secrets.py +0 -0
  34. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v1/sessions.py +0 -0
  35. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v3/__init__.py +0 -0
  36. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v3/attachments.py +0 -0
  37. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v3/consumption.py +0 -0
  38. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v3/knowledge.py +0 -0
  39. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v3/members.py +0 -0
  40. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v3/organizations.py +0 -0
  41. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v3/playbooks.py +0 -0
  42. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v3/repositories.py +0 -0
  43. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v3/schedules.py +0 -0
  44. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v3/secrets.py +0 -0
  45. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/api/v3/sessions.py +0 -0
  46. {devin_cli-1.2.4 → devin_cli-1.3.0}/src/devin_cli/config.py +0 -0
  47. {devin_cli-1.2.4 → devin_cli-1.3.0}/tests/__init__.py +0 -0
  48. {devin_cli-1.2.4 → devin_cli-1.3.0}/tests/conftest.py +0 -0
  49. {devin_cli-1.2.4 → devin_cli-1.3.0}/tests/test_api/test_attachments.py +0 -0
  50. {devin_cli-1.2.4 → devin_cli-1.3.0}/tests/test_api/test_knowledge.py +0 -0
  51. {devin_cli-1.2.4 → devin_cli-1.3.0}/tests/test_api/test_playbooks.py +0 -0
  52. {devin_cli-1.2.4 → devin_cli-1.3.0}/tests/test_api/test_repositories.py +0 -0
  53. {devin_cli-1.2.4 → devin_cli-1.3.0}/tests/test_api/test_schedules.py +0 -0
  54. {devin_cli-1.2.4 → devin_cli-1.3.0}/tests/test_api/test_secrets.py +0 -0
  55. {devin_cli-1.2.4 → devin_cli-1.3.0}/tests/test_api/test_sessions.py +0 -0
  56. {devin_cli-1.2.4 → devin_cli-1.3.0}/tests/test_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devin-cli
3
- Version: 1.2.4
3
+ Version: 1.3.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
@@ -87,7 +87,16 @@ devin watch
87
87
 
88
88
  ---
89
89
 
90
- ## 🔑 Multi-Profile Support
90
+ ## Authentication & Usage
91
+
92
+ ### AI Agent Integration (JSON Output)
93
+ For external automation and AI agent architectures relying on the CLI, `devin-cli` supports a global `--json` flag. This will suppress all visual output/terminal colors and instead uniformly return raw JSON objects for stdout and API errors, making the CLI completely deterministic to parse.
94
+ ```bash
95
+ devin --json sessions create "My prompt"
96
+ # { "session_id": "...", "status": "running" }
97
+ ```
98
+
99
+ ### Multi-Profile Support
91
100
 
92
101
  ```bash
93
102
  devin configure --profile personal
@@ -51,7 +51,16 @@ devin watch
51
51
 
52
52
  ---
53
53
 
54
- ## 🔑 Multi-Profile Support
54
+ ## Authentication & Usage
55
+
56
+ ### AI Agent Integration (JSON Output)
57
+ For external automation and AI agent architectures relying on the CLI, `devin-cli` supports a global `--json` flag. This will suppress all visual output/terminal colors and instead uniformly return raw JSON objects for stdout and API errors, making the CLI completely deterministic to parse.
58
+ ```bash
59
+ devin --json sessions create "My prompt"
60
+ # { "session_id": "...", "status": "running" }
61
+ ```
62
+
63
+ ### Multi-Profile Support
55
64
 
56
65
  ```bash
57
66
  devin configure --profile personal
@@ -0,0 +1,78 @@
1
+ import re
2
+
3
+ with open("src/devin_cli/cli.py", "r") as f:
4
+ lines = f.read().splitlines()
5
+
6
+ # We want to find each `def ..._cmd(...)` block.
7
+ # Then, if it contains `resp = ` and doesn't end with `return`, we insert `return resp`.
8
+
9
+ new_lines = []
10
+ i = 0
11
+
12
+ in_cmd = False
13
+ cmd_indent = ""
14
+ cmd_has_resp = False
15
+ cmd_lines = []
16
+
17
+ def finish_cmd():
18
+ global cmd_lines, new_lines, cmd_has_resp
19
+
20
+ if not cmd_lines: return
21
+
22
+ # check if has resp
23
+ text = "\n".join(cmd_lines)
24
+ if cmd_has_resp and "return" not in cmd_lines[-1]:
25
+ # find the indentation of the last non-empty line
26
+ for l in reversed(cmd_lines):
27
+ if l.strip():
28
+ indent = len(l) - len(l.lstrip())
29
+ break
30
+ else:
31
+ indent = 4
32
+
33
+ # Don't add if it's already there
34
+ if not cmd_lines[-1].strip().startswith("return"):
35
+ cmd_lines.append(" " * indent + "return resp")
36
+
37
+ new_lines.extend(cmd_lines)
38
+ cmd_lines = []
39
+ cmd_has_resp = False
40
+
41
+ while i < len(lines):
42
+ line = lines[i]
43
+
44
+ if line.startswith("def ") and "_cmd(" in line:
45
+ finish_cmd() # finish previous if any
46
+ in_cmd = True
47
+ cmd_indent = "def"
48
+ cmd_lines.append(line)
49
+ elif in_cmd:
50
+ if line.startswith("def "): # another function started
51
+ finish_cmd()
52
+ in_cmd = True
53
+ cmd_lines.append(line)
54
+ elif line and not line.startswith(" ") and not line.startswith("("):
55
+ # top level code or decorator
56
+ if line.startswith("@"):
57
+ finish_cmd()
58
+ new_lines.append(line)
59
+ in_cmd = False
60
+ else:
61
+ finish_cmd()
62
+ new_lines.append(line)
63
+ in_cmd = False
64
+ else:
65
+ if "resp =" in line or "resp = " in line:
66
+ cmd_has_resp = True
67
+ cmd_lines.append(line)
68
+ else:
69
+ new_lines.append(line)
70
+
71
+ i += 1
72
+
73
+ finish_cmd()
74
+
75
+ with open("src/devin_cli/cli.py", "w") as f:
76
+ f.write("\n".join(new_lines) + "\n")
77
+
78
+ print("Patched src/devin_cli/cli.py")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devin-cli"
3
- version = "1.2.4"
3
+ version = "1.3.0"
4
4
  description = "Unofficial CLI for Devin AI - The first AI Software Engineer"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,6 +1,8 @@
1
1
  import typer
2
2
  import time
3
3
  import json
4
+ import requests
5
+ import functools
4
6
  import yaml
5
7
  import asyncio
6
8
  from pathlib import Path
@@ -80,10 +82,17 @@ def main(
80
82
  ctx: typer.Context,
81
83
  profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Configuration profile to use (e.g., service, personal)"),
82
84
  version: Optional[bool] = typer.Option(None, "--version", "-v", help="Show the application's version and exit."),
85
+ json_format: bool = typer.Option(False, "--json", help="Output raw JSON (for AI agents & scripting)"),
83
86
  ):
84
87
  """
85
88
  Unofficial CLI for Devin AI v3.
86
89
  """
90
+ if json_format:
91
+ import os
92
+ import warnings
93
+ os.environ["DEVIN_OUTPUT_FORMAT"] = "json"
94
+ warnings.filterwarnings("ignore")
95
+
87
96
  if version:
88
97
  console.print(f"devin CLI version: {__version__}")
89
98
  raise typer.Exit()
@@ -97,16 +106,50 @@ def handle_api_error(func):
97
106
  """Decorator to handle API errors gracefully."""
98
107
  @functools.wraps(func)
99
108
  def wrapper(*args, **kwargs):
109
+ import os
110
+ is_json = os.environ.get("DEVIN_OUTPUT_FORMAT") == "json"
111
+ original_print = console.print
100
112
  try:
101
- return func(*args, **kwargs)
102
- except APIError as e:
103
- console.print(f"[bold red]Error:[/bold red] {e}")
104
- if e.status_code == 401:
105
- console.print("Tip: Check your API token with 'devin configure'.")
106
- raise typer.Exit(1)
107
- except Exception as e:
108
- console.print(f"[bold red]Unexpected Error:[/bold red] {e}")
109
- raise typer.Exit(1)
113
+ if is_json:
114
+ console.print = lambda *a, **k: None
115
+
116
+ result = func(*args, **kwargs)
117
+
118
+ if is_json:
119
+ console.print = original_print
120
+ if result is not None:
121
+ print(json.dumps(result, indent=2))
122
+
123
+ return result
124
+ except BaseException as e:
125
+ if is_json:
126
+ console.print = original_print
127
+ if isinstance(e, typer.Exit):
128
+ raise e
129
+ elif isinstance(e, requests.exceptions.RequestException):
130
+ if is_json:
131
+ print(json.dumps({"error": "Connection error", "details": str(e)}, indent=2))
132
+ else:
133
+ console.print(f"[bold red]Connection Error:[/bold red] Failed to connect to {config.base_url}")
134
+ console.print(f"[dim]{str(e)}[/dim]")
135
+ raise typer.Exit(1)
136
+ elif hasattr(e, "response"): # Standard APIError handling
137
+ if is_json:
138
+ try:
139
+ err_json = e.response.json()
140
+ except:
141
+ err_json = {"error": str(e)}
142
+ print(json.dumps(err_json, indent=2))
143
+ else:
144
+ console.print(f"[bold red]API Error:[/bold red] {e}")
145
+ raise typer.Exit(1)
146
+ else:
147
+ if is_json:
148
+ print(json.dumps({"error": str(e)}, indent=2))
149
+ else:
150
+ console.print(f"[bold red]Error:[/bold red] {e}")
151
+ raise typer.Exit(1)
152
+
110
153
  return wrapper
111
154
 
112
155
  def get_current_session_id():
@@ -408,6 +451,7 @@ def list_knowledge_cmd(org: Optional[str] = typer.Option(None, "--org")):
408
451
  table.add_row(item.get("id"), item.get("title") or item.get("name"))
409
452
  console.print(table)
410
453
 
454
+ return resp
411
455
  @knowledge_app.command("create")
412
456
  @handle_api_error
413
457
  def create_knowledge_cmd(
@@ -576,6 +620,7 @@ def list_schedules_cmd(org: Optional[str] = typer.Option(None, "--org")):
576
620
  table.add_row(item.get("id"), item.get("title"), item.get("cron"))
577
621
  console.print(table)
578
622
 
623
+ return resp
579
624
  @schedule_app.command("create")
580
625
  @handle_api_error
581
626
  def create_schedule_cmd(
@@ -676,6 +721,7 @@ def whoami_cmd():
676
721
  resp = members.get_self()
677
722
  console.print(Panel(json.dumps(resp, indent=2), title="Who Am I"))
678
723
 
724
+ return resp
679
725
  @enterprise_app.command("list-orgs")
680
726
  @handle_api_error
681
727
  def list_orgs_cmd():
@@ -683,6 +729,7 @@ def list_orgs_cmd():
683
729
  resp = organizations.list_organizations()
684
730
  console.print(Panel(json.dumps(resp, indent=2), title="Organizations"))
685
731
 
732
+ return resp
686
733
  # --- Global Commands ---
687
734
  @app.command("use")
688
735
  def use_session_cmd(session_id: str):
@@ -711,6 +758,7 @@ def status_cmd():
711
758
  if url:
712
759
  console.print(f"[bold]URL:[/bold] [underline]{url}[/underline]")
713
760
 
761
+ return resp
714
762
  @app.command("open")
715
763
  @handle_api_error
716
764
  def open_cmd(
@@ -865,6 +913,7 @@ def upload_cmd(path: Path = typer.Argument(..., help="File to upload")):
865
913
  resp = attachments.upload_file(str(path))
866
914
  console.print(f"[green]Uploaded:[/green] {resp}")
867
915
 
916
+ return resp
868
917
  @app.command("list-knowledge")
869
918
  @handle_api_error
870
919
  def list_knowledge_top_cmd():
@@ -878,6 +927,7 @@ def list_knowledge_top_cmd():
878
927
  table.add_row(item.get("id"), item.get("title") or item.get("name"))
879
928
  console.print(table)
880
929
 
930
+ return resp
881
931
  @app.command("attach")
882
932
  @handle_api_error
883
933
  def attach_cmd(
@@ -1006,6 +1056,7 @@ def list_secrets_top_cmd():
1006
1056
  table.add_row(item.get("id", ""), item.get("name", ""))
1007
1057
  console.print(table)
1008
1058
 
1059
+ return resp
1009
1060
  @app.command("delete-secret")
1010
1061
  @handle_api_error
1011
1062
  def delete_secret_top_cmd(secret_id: str = typer.Argument(...)):
@@ -0,0 +1,46 @@
1
+ import os
2
+ import json
3
+ from functools import wraps
4
+ import builtins
5
+
6
+ class Console:
7
+ def print(self, *args, **kwargs):
8
+ builtins.print("RICH_PRINT:", *args)
9
+
10
+ console = Console()
11
+
12
+ def handle_api_error(func):
13
+ @wraps(func)
14
+ def wrapper(*args, **kwargs):
15
+ try:
16
+ is_json = os.environ.get("DEVIN_OUTPUT_FORMAT") == "json"
17
+ if is_json:
18
+ original_print = console.print
19
+ console.print = lambda *args, **kwargs: None
20
+
21
+ try:
22
+ result = func(*args, **kwargs)
23
+ finally:
24
+ if is_json:
25
+ console.print = original_print
26
+
27
+ if is_json and result is not None:
28
+ builtins.print(json.dumps(result, indent=2))
29
+ return result
30
+ except Exception as e:
31
+ builtins.print("ERROR:", e)
32
+ return wrapper
33
+
34
+ @handle_api_error
35
+ def create_session_cmd():
36
+ console.print("[green]Session created[/green]")
37
+ resp = {"session_id": "123", "status": "running"}
38
+ return resp
39
+
40
+ print("--- NORMAL MODE ---")
41
+ os.environ.pop("DEVIN_OUTPUT_FORMAT", None)
42
+ create_session_cmd()
43
+
44
+ print("\n--- JSON MODE ---")
45
+ os.environ["DEVIN_OUTPUT_FORMAT"] = "json"
46
+ create_session_cmd()
@@ -0,0 +1,47 @@
1
+ import os
2
+ import json
3
+ from functools import wraps
4
+ import builtins
5
+
6
+ class Console:
7
+ def print(self, *args, **kwargs):
8
+ builtins.print("RICH_PRINT:", *args)
9
+
10
+ console = Console()
11
+
12
+ def handle_api_error(func):
13
+ @wraps(func)
14
+ def wrapper(*args, **kwargs):
15
+ try:
16
+ is_json = os.environ.get("DEVIN_OUTPUT_FORMAT") == "json"
17
+ if is_json:
18
+ original_print = console.print
19
+ console.print = lambda *args, **kwargs: None
20
+
21
+ try:
22
+ result = func(*args, **kwargs)
23
+ finally:
24
+ if is_json:
25
+ console.print = original_print
26
+
27
+ if is_json and result is not None:
28
+ builtins.print(json.dumps(result, indent=2))
29
+ return result
30
+ except Exception as e:
31
+ builtins.print("ERROR:", e)
32
+ return wrapper
33
+
34
+ @handle_api_error
35
+ def create_session_cmd():
36
+ console.print("[green]Session created[/green]")
37
+ resp = {"session_id": "123", "status": "running"}
38
+ return resp
39
+
40
+ print("--- NORMAL MODE ---")
41
+ if "DEVIN_OUTPUT_FORMAT" in os.environ:
42
+ del os.environ["DEVIN_OUTPUT_FORMAT"]
43
+ create_session_cmd()
44
+
45
+ print("\n--- JSON MODE ---")
46
+ os.environ["DEVIN_OUTPUT_FORMAT"] = "json"
47
+ create_session_cmd()
@@ -22,6 +22,6 @@ def test_cli_handles_api_error():
22
22
  # command is now 'sessions create'
23
23
  result = runner.invoke(app, ["sessions", "create", "test prompt"])
24
24
  assert result.exit_code == 1
25
- assert "Unexpected Error" in result.stdout
25
+ assert "Error" in result.stdout
26
26
  assert "API BOOM" in result.stdout
27
27
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes