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.
- {devin_cli-1.2.3 → devin_cli-1.3.0}/PKG-INFO +11 -2
- {devin_cli-1.2.3 → devin_cli-1.3.0}/README.md +10 -1
- devin_cli-1.3.0/patch_cli.py +78 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/pyproject.toml +1 -1
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v1/knowledge.py +8 -6
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/knowledge.py +16 -5
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/sessions.py +2 -1
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/cli.py +73 -13
- devin_cli-1.3.0/test_json.py +46 -0
- devin_cli-1.3.0/test_json_wrapper.py +47 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_knowledge.py +5 -3
- {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_cli.py +1 -1
- {devin_cli-1.2.3 → devin_cli-1.3.0}/.github/workflows/pypi-publish.yml +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/.gitignore +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/CHANGELOG.md +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/LICENSE +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/assets/logo.png +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/generate_facades.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/setup.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/__init__.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/__init__.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/attachments.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/client.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/consumption.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/knowledge.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/members.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/organizations.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/playbooks.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/repositories.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/schedules.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/secrets.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/sessions.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v1/__init__.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v1/attachments.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v1/playbooks.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v1/secrets.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v1/sessions.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/__init__.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/attachments.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/consumption.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/members.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/organizations.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/playbooks.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/repositories.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/schedules.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/api/v3/secrets.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/src/devin_cli/config.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/__init__.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/conftest.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_attachments.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_playbooks.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_repositories.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_schedules.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_secrets.py +0 -0
- {devin_cli-1.2.3 → devin_cli-1.3.0}/tests/test_api/test_sessions.py +0 -0
- {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.
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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")
|
|
@@ -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
|
-
"
|
|
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["
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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(
|
|
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["
|
|
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 "
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|