devin-cli 1.2.3__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.3 → devin_cli-1.3.0}/PKG-INFO +11 -2
  2. {devin_cli-1.2.3 → 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.3 → devin_cli-1.3.0}/pyproject.toml +1 -1
  5. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v1/knowledge.py +8 -6
  6. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/knowledge.py +16 -5
  7. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/sessions.py +2 -1
  8. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/cli.py +73 -13
  9. devin_cli-1.3.0/test_json.py +46 -0
  10. devin_cli-1.3.0/test_json_wrapper.py +47 -0
  11. {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_knowledge.py +5 -3
  12. {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_cli.py +1 -1
  13. {devin_cli-1.2.3 → devin_cli-1.3.0}/.github/workflows/pypi-publish.yml +0 -0
  14. {devin_cli-1.2.3 → devin_cli-1.3.0}/.gitignore +0 -0
  15. {devin_cli-1.2.3 → devin_cli-1.3.0}/CHANGELOG.md +0 -0
  16. {devin_cli-1.2.3 → devin_cli-1.3.0}/LICENSE +0 -0
  17. {devin_cli-1.2.3 → devin_cli-1.3.0}/assets/logo.png +0 -0
  18. {devin_cli-1.2.3 → devin_cli-1.3.0}/generate_facades.py +0 -0
  19. {devin_cli-1.2.3 → devin_cli-1.3.0}/setup.py +0 -0
  20. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/__init__.py +0 -0
  21. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/__init__.py +0 -0
  22. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/attachments.py +0 -0
  23. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/client.py +0 -0
  24. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/consumption.py +0 -0
  25. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/knowledge.py +0 -0
  26. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/members.py +0 -0
  27. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/organizations.py +0 -0
  28. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/playbooks.py +0 -0
  29. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/repositories.py +0 -0
  30. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/schedules.py +0 -0
  31. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/secrets.py +0 -0
  32. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/sessions.py +0 -0
  33. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v1/__init__.py +0 -0
  34. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v1/attachments.py +0 -0
  35. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v1/playbooks.py +0 -0
  36. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v1/secrets.py +0 -0
  37. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v1/sessions.py +0 -0
  38. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/__init__.py +0 -0
  39. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/attachments.py +0 -0
  40. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/consumption.py +0 -0
  41. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/members.py +0 -0
  42. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/organizations.py +0 -0
  43. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/playbooks.py +0 -0
  44. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/repositories.py +0 -0
  45. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/schedules.py +0 -0
  46. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/secrets.py +0 -0
  47. {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/config.py +0 -0
  48. {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/__init__.py +0 -0
  49. {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/conftest.py +0 -0
  50. {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_attachments.py +0 -0
  51. {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_playbooks.py +0 -0
  52. {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_repositories.py +0 -0
  53. {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_schedules.py +0 -0
  54. {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_secrets.py +0 -0
  55. {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_sessions.py +0 -0
  56. {devin_cli-1.2.3 → 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.3
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.3"
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 = [
@@ -5,18 +5,19 @@ def list_knowledge():
5
5
  return client.get("knowledge")
6
6
 
7
7
  def create_knowledge(
8
- name: str,
9
- body: str,
10
- trigger_description: str,
8
+ name: str = None,
9
+ body: str = None,
10
+ trigger_description: str = None,
11
11
  parent_folder_id: str = None,
12
12
  pinned_repo: str = None,
13
13
  title: str = None, # v3 fallback
14
+ trigger: str = None, # v3 fallback
14
15
  **kwargs
15
16
  ):
16
17
  data = {
17
18
  "name": name or title,
18
19
  "body": body,
19
- "trigger_description": trigger_description or ""
20
+ "trigger_description": trigger_description or trigger or ""
20
21
  }
21
22
  if parent_folder_id:
22
23
  data["parent_folder_id"] = parent_folder_id
@@ -31,6 +32,7 @@ def update_knowledge(
31
32
  body: str = None,
32
33
  trigger_description: str = None,
33
34
  title: str = None, # v3 fallback
35
+ trigger: str = None, # v3 fallback
34
36
  **kwargs
35
37
  ):
36
38
  data = {}
@@ -38,8 +40,8 @@ def update_knowledge(
38
40
  data["name"] = name or title
39
41
  if body:
40
42
  data["body"] = body
41
- if trigger_description:
42
- data["trigger_description"] = trigger_description
43
+ if trigger_description or trigger:
44
+ data["trigger_description"] = trigger_description or trigger
43
45
 
44
46
  return client.put(f"knowledge/{knowledge_id}", json=data)
45
47
 
@@ -5,12 +5,17 @@ def list_knowledge():
5
5
  return client.get("knowledge/notes")
6
6
 
7
7
  def create_knowledge(
8
- title: str,
9
- body: str,
8
+ title: str = None,
9
+ body: str = None,
10
+ name: str = None,
11
+ trigger: str = "",
12
+ trigger_description: str = "",
13
+ **kwargs
10
14
  ):
11
15
  data = {
12
- "title": title,
16
+ "name": name or title,
13
17
  "body": body,
18
+ "trigger": trigger or trigger_description or ""
14
19
  }
15
20
  return client.post("knowledge/notes", json=data)
16
21
 
@@ -21,12 +26,18 @@ def update_knowledge(
21
26
  note_id: str,
22
27
  title: Optional[str] = None,
23
28
  body: Optional[str] = None,
29
+ trigger: Optional[str] = None,
30
+ name: Optional[str] = None,
31
+ trigger_description: Optional[str] = None,
32
+ **kwargs
24
33
  ):
25
34
  data = {}
26
- if title:
27
- data["title"] = title
35
+ if title or name:
36
+ data["name"] = title or name
28
37
  if body:
29
38
  data["body"] = body
39
+ if trigger or trigger_description:
40
+ data["trigger"] = trigger or trigger_description
30
41
 
31
42
  return client.put(f"knowledge/notes/{note_id}", json=data)
32
43
 
@@ -27,8 +27,9 @@ def create_session(
27
27
  ):
28
28
  data = {
29
29
  "prompt": prompt,
30
- "bypass_approval": bypass_approval,
31
30
  }
31
+ if bypass_approval:
32
+ data["bypass_approval"] = bypass_approval
32
33
  if advanced_mode:
33
34
  data["advanced_mode"] = advanced_mode
34
35
  if attachment_urls:
@@ -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,16 +451,18 @@ 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(
414
458
  title: str,
415
459
  body: str,
460
+ trigger: str = typer.Option("", "--trigger", help="Trigger description"),
416
461
  org: Optional[str] = typer.Option(None, "--org"),
417
462
  ):
418
463
  """Create a knowledge note."""
419
464
  if org: config.temporary_org_id = org
420
- resp = knowledge.create_knowledge(title, body)
465
+ resp = knowledge.create_knowledge(title=title, body=body, trigger=trigger)
421
466
  console.print(f"[green]Created note:[/green] {resp.get('id')}")
422
467
 
423
468
  @knowledge_app.command("delete")
@@ -435,13 +480,21 @@ def delete_knowledge_cmd(
435
480
  # --- Playbooks ---
436
481
  @playbook_app.command("list")
437
482
  @handle_api_error
438
- def list_playbooks_cmd(org: Optional[str] = typer.Option(None, "--org")):
483
+ def list_playbooks_cmd(
484
+ org: Optional[str] = typer.Option(None, "--org"),
485
+ json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
486
+ ):
439
487
  """List team playbooks."""
440
488
  if org: config.temporary_org_id = org
441
489
  resp = playbooks.list_playbooks()
442
490
  items = resp.get("items", []) if isinstance(resp, dict) else resp
491
+
492
+ if json_output:
493
+ console.print(json.dumps(items, indent=2))
494
+ return
495
+
443
496
  table = Table(title="Playbooks")
444
- table.add_column("ID", style="cyan")
497
+ table.add_column("ID", style="cyan", no_wrap=True)
445
498
  table.add_column("Title")
446
499
  for item in items:
447
500
  if isinstance(item, dict):
@@ -567,6 +620,7 @@ def list_schedules_cmd(org: Optional[str] = typer.Option(None, "--org")):
567
620
  table.add_row(item.get("id"), item.get("title"), item.get("cron"))
568
621
  console.print(table)
569
622
 
623
+ return resp
570
624
  @schedule_app.command("create")
571
625
  @handle_api_error
572
626
  def create_schedule_cmd(
@@ -667,6 +721,7 @@ def whoami_cmd():
667
721
  resp = members.get_self()
668
722
  console.print(Panel(json.dumps(resp, indent=2), title="Who Am I"))
669
723
 
724
+ return resp
670
725
  @enterprise_app.command("list-orgs")
671
726
  @handle_api_error
672
727
  def list_orgs_cmd():
@@ -674,6 +729,7 @@ def list_orgs_cmd():
674
729
  resp = organizations.list_organizations()
675
730
  console.print(Panel(json.dumps(resp, indent=2), title="Organizations"))
676
731
 
732
+ return resp
677
733
  # --- Global Commands ---
678
734
  @app.command("use")
679
735
  def use_session_cmd(session_id: str):
@@ -702,6 +758,7 @@ def status_cmd():
702
758
  if url:
703
759
  console.print(f"[bold]URL:[/bold] [underline]{url}[/underline]")
704
760
 
761
+ return resp
705
762
  @app.command("open")
706
763
  @handle_api_error
707
764
  def open_cmd(
@@ -856,6 +913,7 @@ def upload_cmd(path: Path = typer.Argument(..., help="File to upload")):
856
913
  resp = attachments.upload_file(str(path))
857
914
  console.print(f"[green]Uploaded:[/green] {resp}")
858
915
 
916
+ return resp
859
917
  @app.command("list-knowledge")
860
918
  @handle_api_error
861
919
  def list_knowledge_top_cmd():
@@ -869,6 +927,7 @@ def list_knowledge_top_cmd():
869
927
  table.add_row(item.get("id"), item.get("title") or item.get("name"))
870
928
  console.print(table)
871
929
 
930
+ return resp
872
931
  @app.command("attach")
873
932
  @handle_api_error
874
933
  def attach_cmd(
@@ -950,7 +1009,7 @@ def update_knowledge_cmd(
950
1009
  trigger: Optional[str] = typer.Option(None, "--trigger"),
951
1010
  ):
952
1011
  """Update an existing knowledge entry."""
953
- knowledge.update_knowledge(knowledge_id, title=name, body=body)
1012
+ knowledge.update_knowledge(knowledge_id, title=name, body=body, trigger=trigger)
954
1013
  console.print(f"[green]Knowledge {knowledge_id} updated.[/green]")
955
1014
 
956
1015
  @app.command("update-playbook")
@@ -997,6 +1056,7 @@ def list_secrets_top_cmd():
997
1056
  table.add_row(item.get("id", ""), item.get("name", ""))
998
1057
  console.print(table)
999
1058
 
1059
+ return resp
1000
1060
  @app.command("delete-secret")
1001
1061
  @handle_api_error
1002
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()
@@ -24,19 +24,21 @@ def test_create_knowledge():
24
24
  )
25
25
  knowledge.create_knowledge(
26
26
  title="test",
27
- body="content"
27
+ body="content",
28
+ trigger="my_trigger"
28
29
  )
29
30
  assert route.called
30
31
  import json
31
32
  body = json.loads(route.calls.last.request.read())
32
- assert body["title"] == "test"
33
+ assert body["name"] == "test"
34
+ assert body["trigger"] == "my_trigger"
33
35
 
34
36
  @respx.mock
35
37
  def test_update_knowledge():
36
38
  route = respx.put("https://api.devin.ai/v3/organizations/test_org/knowledge/notes/k_123").mock(
37
39
  return_value=Response(200)
38
40
  )
39
- knowledge.update_knowledge("k_123", title="new")
41
+ knowledge.update_knowledge("k_123", title="new", trigger="new_trigger")
40
42
  assert route.called
41
43
 
42
44
  @respx.mock
@@ -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