devin-cli 1.2.4__tar.gz → 1.3.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.
Files changed (53) hide show
  1. {devin_cli-1.2.4 → devin_cli-1.3.1}/CHANGELOG.md +10 -0
  2. {devin_cli-1.2.4 → devin_cli-1.3.1}/PKG-INFO +11 -2
  3. {devin_cli-1.2.4 → devin_cli-1.3.1}/README.md +10 -1
  4. {devin_cli-1.2.4 → devin_cli-1.3.1}/pyproject.toml +1 -1
  5. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v1/knowledge.py +3 -0
  6. {devin_cli-1.2.4/src/devin_cli/api/v1 → devin_cli-1.3.1/src/devin_cli/api/v3}/secrets.py +3 -0
  7. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/cli.py +157 -15
  8. {devin_cli-1.2.4 → devin_cli-1.3.1}/tests/test_cli.py +1 -1
  9. {devin_cli-1.2.4 → devin_cli-1.3.1}/.github/workflows/pypi-publish.yml +0 -0
  10. {devin_cli-1.2.4 → devin_cli-1.3.1}/.gitignore +0 -0
  11. {devin_cli-1.2.4 → devin_cli-1.3.1}/LICENSE +0 -0
  12. {devin_cli-1.2.4 → devin_cli-1.3.1}/assets/logo.png +0 -0
  13. {devin_cli-1.2.4 → devin_cli-1.3.1}/generate_facades.py +0 -0
  14. {devin_cli-1.2.4 → devin_cli-1.3.1}/setup.py +0 -0
  15. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/__init__.py +0 -0
  16. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/__init__.py +0 -0
  17. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/attachments.py +0 -0
  18. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/client.py +0 -0
  19. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/consumption.py +0 -0
  20. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/knowledge.py +0 -0
  21. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/members.py +0 -0
  22. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/organizations.py +0 -0
  23. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/playbooks.py +0 -0
  24. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/repositories.py +0 -0
  25. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/schedules.py +0 -0
  26. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/secrets.py +0 -0
  27. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/sessions.py +0 -0
  28. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v1/__init__.py +0 -0
  29. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v1/attachments.py +0 -0
  30. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v1/playbooks.py +0 -0
  31. {devin_cli-1.2.4/src/devin_cli/api/v3 → devin_cli-1.3.1/src/devin_cli/api/v1}/secrets.py +0 -0
  32. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v1/sessions.py +0 -0
  33. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v3/__init__.py +0 -0
  34. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v3/attachments.py +0 -0
  35. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v3/consumption.py +0 -0
  36. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v3/knowledge.py +0 -0
  37. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v3/members.py +0 -0
  38. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v3/organizations.py +0 -0
  39. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v3/playbooks.py +0 -0
  40. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v3/repositories.py +0 -0
  41. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v3/schedules.py +0 -0
  42. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/api/v3/sessions.py +0 -0
  43. {devin_cli-1.2.4 → devin_cli-1.3.1}/src/devin_cli/config.py +0 -0
  44. {devin_cli-1.2.4 → devin_cli-1.3.1}/tests/__init__.py +0 -0
  45. {devin_cli-1.2.4 → devin_cli-1.3.1}/tests/conftest.py +0 -0
  46. {devin_cli-1.2.4 → devin_cli-1.3.1}/tests/test_api/test_attachments.py +0 -0
  47. {devin_cli-1.2.4 → devin_cli-1.3.1}/tests/test_api/test_knowledge.py +0 -0
  48. {devin_cli-1.2.4 → devin_cli-1.3.1}/tests/test_api/test_playbooks.py +0 -0
  49. {devin_cli-1.2.4 → devin_cli-1.3.1}/tests/test_api/test_repositories.py +0 -0
  50. {devin_cli-1.2.4 → devin_cli-1.3.1}/tests/test_api/test_schedules.py +0 -0
  51. {devin_cli-1.2.4 → devin_cli-1.3.1}/tests/test_api/test_secrets.py +0 -0
  52. {devin_cli-1.2.4 → devin_cli-1.3.1}/tests/test_api/test_sessions.py +0 -0
  53. {devin_cli-1.2.4 → devin_cli-1.3.1}/tests/test_config.py +0 -0
@@ -5,6 +5,16 @@ 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.3.1] - 2026-03-16
9
+ ### Added
10
+ - **Global `--json` Output**: New orchestration flag that converts *all* CLI output and API errors into pure, parseable JSON for AI agents.
11
+ - **Knowledge Get Command**: Added `devin knowledge get <id>` and `devin get-knowledge <id>` to retrieve full note content.
12
+ - Missing v1 and v3 `get_knowledge` / `create_secret` cross-compatibility polyfills.
13
+
14
+ ### Fixed
15
+ - **Audit Fixes**: Resolved 13 critical bugs found during full codebase audit (indentation errors, missing returns, duplicate imports, and terminal status handling).
16
+ - **Warning Suppression**: Suppressed `urllib3` SSL warnings in JSON mode to ensure clean stdout streams.
17
+
8
18
  ## [1.2.0] - 2026-03-11
9
19
  ### Added
10
20
  - `sessions create` / `create-session` now exposes all 8 previously missing v3 API parameters: `--playbook-id`, `--tag`, `--repo`, `--knowledge-id`, `--secret-id`, `--session-link`, `--attachment-url`, `--create-as-user-id`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devin-cli
3
- Version: 1.2.4
3
+ Version: 1.3.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
@@ -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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devin-cli"
3
- version = "1.2.4"
3
+ version = "1.3.1"
4
4
  description = "Unofficial CLI for Devin AI - The first AI Software Engineer"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -45,5 +45,8 @@ def update_knowledge(
45
45
 
46
46
  return client.put(f"knowledge/{knowledge_id}", json=data)
47
47
 
48
+ def get_knowledge(knowledge_id: str):
49
+ return client.get(f"knowledge/{knowledge_id}")
50
+
48
51
  def delete_knowledge(knowledge_id: str):
49
52
  return client.delete(f"knowledge/{knowledge_id}")
@@ -3,5 +3,8 @@ from devin_cli.api.client import client
3
3
  def list_secrets():
4
4
  return client.get("secrets")
5
5
 
6
+ def create_secret(name: str, value: str):
7
+ return client.post("secrets", json={"name": name, "value": value})
8
+
6
9
  def delete_secret(secret_id: str):
7
10
  return client.delete(f"secrets/{secret_id}")
@@ -1,6 +1,17 @@
1
+ import warnings
2
+ import os
3
+ import sys
4
+
5
+ # Suppress urllib3 warnings early for clean JSON output
6
+ if "--json" in sys.argv or os.environ.get("DEVIN_OUTPUT_FORMAT") == "json":
7
+ warnings.filterwarnings("ignore")
8
+ os.environ["DEVIN_OUTPUT_FORMAT"] = "json"
9
+
1
10
  import typer
2
11
  import time
3
12
  import json
13
+ import requests
14
+ import functools
4
15
  import yaml
5
16
  import asyncio
6
17
  from pathlib import Path
@@ -14,7 +25,6 @@ from devin_cli.config import config
14
25
  from devin_cli.api import sessions, knowledge, playbooks, secrets, attachments, repositories, schedules, organizations, members, consumption
15
26
  from devin_cli.api.client import client, APIError
16
27
  import webbrowser
17
- import functools
18
28
  import sys
19
29
  import hashlib
20
30
  import importlib.metadata
@@ -80,10 +90,14 @@ def main(
80
90
  ctx: typer.Context,
81
91
  profile: Optional[str] = typer.Option(None, "--profile", "-p", help="Configuration profile to use (e.g., service, personal)"),
82
92
  version: Optional[bool] = typer.Option(None, "--version", "-v", help="Show the application's version and exit."),
93
+ json_format: bool = typer.Option(False, "--json", help="Output raw JSON (for AI agents & scripting)"),
83
94
  ):
84
95
  """
85
96
  Unofficial CLI for Devin AI v3.
86
97
  """
98
+ if json_format:
99
+ os.environ["DEVIN_OUTPUT_FORMAT"] = "json"
100
+
87
101
  if version:
88
102
  console.print(f"devin CLI version: {__version__}")
89
103
  raise typer.Exit()
@@ -97,16 +111,50 @@ def handle_api_error(func):
97
111
  """Decorator to handle API errors gracefully."""
98
112
  @functools.wraps(func)
99
113
  def wrapper(*args, **kwargs):
114
+ import os
115
+ is_json = os.environ.get("DEVIN_OUTPUT_FORMAT") == "json"
116
+ original_print = console.print
100
117
  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)
118
+ if is_json:
119
+ console.print = lambda *a, **k: None
120
+
121
+ result = func(*args, **kwargs)
122
+
123
+ if is_json:
124
+ console.print = original_print
125
+ if result is not None:
126
+ print(json.dumps(result, indent=2))
127
+
128
+ return result
129
+ except BaseException as e:
130
+ if is_json:
131
+ console.print = original_print
132
+ if isinstance(e, typer.Exit):
133
+ raise e
134
+ elif isinstance(e, requests.exceptions.RequestException):
135
+ if is_json:
136
+ print(json.dumps({"error": "Connection error", "details": str(e)}, indent=2))
137
+ else:
138
+ console.print(f"[bold red]Connection Error:[/bold red] Failed to connect to {config.base_url}")
139
+ console.print(f"[dim]{str(e)}[/dim]")
140
+ raise typer.Exit(1)
141
+ elif hasattr(e, "response"): # Standard APIError handling
142
+ if is_json:
143
+ try:
144
+ err_json = e.response.json()
145
+ except:
146
+ err_json = {"error": str(e)}
147
+ print(json.dumps(err_json, indent=2))
148
+ else:
149
+ console.print(f"[bold red]API Error:[/bold red] {e}")
150
+ raise typer.Exit(1)
151
+ else:
152
+ if is_json:
153
+ print(json.dumps({"error": str(e)}, indent=2))
154
+ else:
155
+ console.print(f"[bold red]Error:[/bold red] {e}")
156
+ raise typer.Exit(1)
157
+
110
158
  return wrapper
111
159
 
112
160
  def get_current_session_id():
@@ -145,6 +193,7 @@ def configure(
145
193
  if org_id:
146
194
  config.org_id = org_id
147
195
  console.print(f"[green]Configuration saved to {config.config_file}[/green]")
196
+ return None
148
197
 
149
198
  # --- Sessions ---
150
199
  @session_app.command("create")
@@ -260,6 +309,8 @@ def create_session_cmd(
260
309
  break
261
310
  else:
262
311
  console.print("[yellow]Session created, but no ID returned immediately (awaiting advanced mode setup).[/yellow]")
312
+
313
+ return resp
263
314
 
264
315
 
265
316
  @session_app.command("list")
@@ -287,6 +338,8 @@ def list_sessions_cmd(
287
338
  for s in sess_list:
288
339
  table.add_row(s.get("session_id"), s.get("status_enum"), s.get("title") or s.get("prompt", "")[:50])
289
340
  console.print(table)
341
+
342
+ return resp
290
343
 
291
344
  @session_app.command("get")
292
345
  @handle_api_error
@@ -299,6 +352,8 @@ def get_session_cmd(
299
352
  sid = session_id or get_current_session_id()
300
353
  resp = sessions.get_session(sid)
301
354
  console.print(Panel(json.dumps(resp, indent=2), title=f"Session {sid}"))
355
+
356
+ return resp
302
357
 
303
358
  @session_app.command("insights")
304
359
  @handle_api_error
@@ -315,6 +370,8 @@ def session_insights_cmd(
315
370
  console.print("Tip: Switch to v3 with [bold cyan]devin configure[/bold cyan] or use [bold cyan]--profile [v3-profile][/bold cyan]")
316
371
  else:
317
372
  console.print(Panel(json.dumps(resp, indent=2), title=f"Insights for {sid}"))
373
+
374
+ return resp
318
375
 
319
376
  @session_app.command("cost")
320
377
  @handle_api_error
@@ -338,6 +395,8 @@ def session_cost_cmd(
338
395
  else:
339
396
  resp = consumption.get_daily_consumption_breakdown()
340
397
  console.print(Panel(json.dumps(resp, indent=2), title="Daily Consumption"))
398
+
399
+ return resp
341
400
 
342
401
  @session_app.command("message")
343
402
  @handle_api_error
@@ -362,6 +421,7 @@ def send_message_cmd(
362
421
  raise typer.Exit(1)
363
422
  sessions.send_message(sid, msg_text)
364
423
  console.print(f"[green]Message sent to {sid}[/green]")
424
+ return None
365
425
 
366
426
  @session_app.command("messages")
367
427
  @handle_api_error
@@ -379,6 +439,8 @@ def list_messages_cmd(
379
439
  content = m.get("message", "") or m.get("content", "")
380
440
  console.print(f"[bold cyan]{role}:[/bold cyan] {content}")
381
441
  console.print("---")
442
+
443
+ return resp
382
444
 
383
445
  @session_app.command("terminate")
384
446
  @handle_api_error
@@ -392,6 +454,7 @@ def terminate_session_cmd(
392
454
  if typer.confirm(f"Terminate session {sid}?"):
393
455
  sessions.terminate_session(sid)
394
456
  console.print(f"[green]Session {sid} terminated.[/green]")
457
+ return None
395
458
 
396
459
  # --- Knowledge ---
397
460
  @knowledge_app.command("list")
@@ -408,6 +471,7 @@ def list_knowledge_cmd(org: Optional[str] = typer.Option(None, "--org")):
408
471
  table.add_row(item.get("id"), item.get("title") or item.get("name"))
409
472
  console.print(table)
410
473
 
474
+ return resp
411
475
  @knowledge_app.command("create")
412
476
  @handle_api_error
413
477
  def create_knowledge_cmd(
@@ -420,6 +484,21 @@ def create_knowledge_cmd(
420
484
  if org: config.temporary_org_id = org
421
485
  resp = knowledge.create_knowledge(title=title, body=body, trigger=trigger)
422
486
  console.print(f"[green]Created note:[/green] {resp.get('id')}")
487
+
488
+ return resp
489
+
490
+
491
+ @knowledge_app.command("get")
492
+ @handle_api_error
493
+ def get_knowledge_cmd(
494
+ knowledge_id: str,
495
+ org: Optional[str] = typer.Option(None, "--org"),
496
+ ):
497
+ """Get detailed content of a knowledge note."""
498
+ if org: config.temporary_org_id = org
499
+ resp = knowledge.get_knowledge(knowledge_id)
500
+ console.print(Panel(json.dumps(resp, indent=2), title=f"Knowledge: {knowledge_id}"))
501
+ return resp
423
502
 
424
503
  @knowledge_app.command("delete")
425
504
  @handle_api_error
@@ -432,6 +511,8 @@ def delete_knowledge_cmd(
432
511
  if typer.confirm(f"Delete knowledge {knowledge_id}?"):
433
512
  knowledge.delete_knowledge(knowledge_id)
434
513
  console.print(f"[green]Knowledge {knowledge_id} deleted.[/green]")
514
+
515
+ return None
435
516
 
436
517
  # --- Playbooks ---
437
518
  @playbook_app.command("list")
@@ -456,6 +537,7 @@ def list_playbooks_cmd(
456
537
  if isinstance(item, dict):
457
538
  table.add_row(item.get("playbook_id", ""), item.get("title", ""))
458
539
  console.print(table)
540
+ return resp
459
541
 
460
542
  @playbook_app.command("update")
461
543
  @handle_api_error
@@ -476,6 +558,8 @@ def update_playbook_cmd(
476
558
  if org: config.temporary_org_id = org
477
559
  resp = playbooks.update_playbook(playbook_id, title=title, body=body, macro=macro)
478
560
  console.print(f"[green]Playbook updated:[/green] {playbook_id}")
561
+
562
+ return resp
479
563
 
480
564
  @playbook_app.command("create")
481
565
  @handle_api_error
@@ -495,6 +579,8 @@ def create_playbook_cmd(
495
579
  if org: config.temporary_org_id = org
496
580
  resp = playbooks.create_playbook(title, body, macro)
497
581
  console.print(f"[green]Playbook created:[/green] {resp.get('playbook_id')}")
582
+
583
+ return resp
498
584
 
499
585
  @playbook_app.command("delete")
500
586
  @handle_api_error
@@ -507,6 +593,7 @@ def delete_playbook_cmd(
507
593
  if typer.confirm(f"Delete playbook {playbook_id}?"):
508
594
  playbooks.delete_playbook(playbook_id)
509
595
  console.print(f"[green]Playbook {playbook_id} deleted.[/green]")
596
+ return None
510
597
 
511
598
  # --- Secrets ---
512
599
  @secret_app.command("list")
@@ -535,6 +622,7 @@ def list_secrets_cmd(
535
622
  if isinstance(item, dict):
536
623
  table.add_row(item.get("id"), item.get("name"))
537
624
  console.print(table)
625
+ return resp
538
626
 
539
627
  @secret_app.command("delete")
540
628
  @handle_api_error
@@ -547,6 +635,7 @@ def delete_secret_cmd(
547
635
  if typer.confirm(f"Delete secret {secret_id}?"):
548
636
  secrets.delete_secret(secret_id)
549
637
  console.print(f"[green]Secret {secret_id} deleted.[/green]")
638
+ return None
550
639
 
551
640
  @secret_app.command("create")
552
641
  @handle_api_error
@@ -559,6 +648,8 @@ def create_secret_cmd(
559
648
  if org: config.temporary_org_id = org
560
649
  secrets.create_secret(name, value)
561
650
  console.print(f"[green]Secret '{name}' created.[/green]")
651
+
652
+ return None
562
653
 
563
654
  # --- Schedules ---
564
655
  @schedule_app.command("list")
@@ -575,7 +666,9 @@ def list_schedules_cmd(org: Optional[str] = typer.Option(None, "--org")):
575
666
  for item in items:
576
667
  table.add_row(item.get("id"), item.get("title"), item.get("cron"))
577
668
  console.print(table)
669
+
578
670
 
671
+ return resp
579
672
  @schedule_app.command("create")
580
673
  @handle_api_error
581
674
  def create_schedule_cmd(
@@ -589,6 +682,8 @@ def create_schedule_cmd(
589
682
  resp = schedules.create_schedule(prompt=prompt, cron=cron, title=title)
590
683
  console.print(f"[green]Created schedule:[/green] {resp.get('id')}")
591
684
 
685
+ return resp
686
+
592
687
  # --- Repositories ---
593
688
  @repo_app.command("list")
594
689
  @handle_api_error
@@ -629,6 +724,7 @@ def list_repos_cmd(
629
724
  )
630
725
  table.add_row(path, "Yes" if indexed else "No")
631
726
  console.print(table)
727
+ return resp
632
728
 
633
729
  @repo_app.command("index")
634
730
  @handle_api_error
@@ -641,6 +737,8 @@ def index_repo_cmd(
641
737
  repositories.index_repository(path)
642
738
  console.print(f"[green]Indexing started for {path}[/green]")
643
739
 
740
+ return None
741
+
644
742
  # --- Attachments ---
645
743
  @attachment_app.command("upload")
646
744
  @handle_api_error
@@ -652,6 +750,7 @@ def upload_attachment_cmd(
652
750
  if org: config.temporary_org_id = org
653
751
  resp = attachments.upload_file(str(path))
654
752
  console.print(f"[green]Uploaded:[/green] {resp}")
753
+ return resp
655
754
 
656
755
  @attachment_app.command("download")
657
756
  @handle_api_error
@@ -668,6 +767,8 @@ def download_attachment_cmd(
668
767
  out_path.write_bytes(content)
669
768
  console.print(f"[green]Downloaded to:[/green] {out_path}")
670
769
 
770
+ return None
771
+
671
772
  # --- Enterprise Discovery ---
672
773
  @enterprise_app.command("whoami")
673
774
  @handle_api_error
@@ -676,6 +777,7 @@ def whoami_cmd():
676
777
  resp = members.get_self()
677
778
  console.print(Panel(json.dumps(resp, indent=2), title="Who Am I"))
678
779
 
780
+ return resp
679
781
  @enterprise_app.command("list-orgs")
680
782
  @handle_api_error
681
783
  def list_orgs_cmd():
@@ -683,12 +785,14 @@ def list_orgs_cmd():
683
785
  resp = organizations.list_organizations()
684
786
  console.print(Panel(json.dumps(resp, indent=2), title="Organizations"))
685
787
 
788
+ return resp
686
789
  # --- Global Commands ---
687
790
  @app.command("use")
688
791
  def use_session_cmd(session_id: str):
689
792
  """Switch the current active session."""
690
793
  config.current_session_id = session_id
691
794
  console.print(f"[green]Switched to session {session_id}[/green]")
795
+ return {"current_session_id": session_id}
692
796
 
693
797
  @app.command("status")
694
798
  @handle_api_error
@@ -711,6 +815,7 @@ def status_cmd():
711
815
  if url:
712
816
  console.print(f"[bold]URL:[/bold] [underline]{url}[/underline]")
713
817
 
818
+ return resp
714
819
  @app.command("open")
715
820
  @handle_api_error
716
821
  def open_cmd(
@@ -726,6 +831,8 @@ def open_cmd(
726
831
  webbrowser.open(url)
727
832
  console.print(f"[green]Opened:[/green] {url}")
728
833
 
834
+ return resp
835
+
729
836
  @session_app.command("watch")
730
837
  @handle_api_error
731
838
  def watch_session_cmd(
@@ -759,7 +866,6 @@ def watch_session_cmd(
759
866
  text.append("\nStructured Output:\n", style="bold")
760
867
  text.append(json.dumps(so, indent=2))
761
868
  text.append("\nPress Ctrl+C to stop watching.", style="dim")
762
- return Panel(text, title="[bold cyan]Devin Watch[/bold cyan]", border_style="cyan")
763
869
 
764
870
  console.print(f"[bold cyan]Watching session {sid}[/bold cyan] (Ctrl+C to stop)")
765
871
 
@@ -780,6 +886,7 @@ def watch_session_cmd(
780
886
  return
781
887
 
782
888
  console.print(f"[bold green]Session {resp.get('status_enum')}![/bold green]")
889
+ return resp
783
890
 
784
891
  # --- Flat top-level aliases (backward compat with 0.1.x command structure) ---
785
892
 
@@ -791,6 +898,7 @@ def watch_cmd(
791
898
  ):
792
899
  """Alias for: devin sessions watch"""
793
900
  watch_session_cmd(session_id=session_id, interval=interval, org=None)
901
+ return None
794
902
 
795
903
  @app.command("message")
796
904
  @handle_api_error
@@ -813,6 +921,7 @@ def message_cmd(
813
921
  raise typer.Exit(1)
814
922
  sessions.send_message(sid, msg_text)
815
923
  console.print(f"[green]Message sent to {sid}[/green]")
924
+ return None
816
925
 
817
926
  @app.command("terminate")
818
927
  @handle_api_error
@@ -824,6 +933,7 @@ def terminate_cmd(
824
933
  if typer.confirm(f"Terminate session {sid}?"):
825
934
  sessions.terminate_session(sid)
826
935
  console.print(f"[green]Session {sid} terminated.[/green]")
936
+ return None
827
937
 
828
938
  @app.command("list-sessions")
829
939
  @handle_api_error
@@ -844,6 +954,7 @@ def list_sessions_top_cmd(
844
954
  for s in sess_list:
845
955
  table.add_row(s.get("session_id"), s.get("status_enum"), s.get("title") or s.get("prompt", "")[:50])
846
956
  console.print(table)
957
+ return resp
847
958
 
848
959
  @app.command("create-session")
849
960
  @handle_api_error
@@ -856,7 +967,12 @@ def create_session_top_cmd(
856
967
  ):
857
968
  """Create a new session (alias for: devin sessions create)."""
858
969
  create_session_cmd(prompt=prompt, file=None, title=title, org=None, max_acu=None,
859
- advanced_mode=None, force=force, wait=wait, interval=interval)
970
+ advanced_mode=None, playbook_id=None, child_playbook_id=None,
971
+ tags=None, repos=None, knowledge_ids=None, secret_ids=None,
972
+ session_links=None, attachment_urls=None, create_as_user_id=None,
973
+ bypass_approval=False, structured_output_schema=None,
974
+ force=force, wait=wait, interval=interval)
975
+ return None
860
976
 
861
977
  @app.command("upload")
862
978
  @handle_api_error
@@ -865,6 +981,7 @@ def upload_cmd(path: Path = typer.Argument(..., help="File to upload")):
865
981
  resp = attachments.upload_file(str(path))
866
982
  console.print(f"[green]Uploaded:[/green] {resp}")
867
983
 
984
+ return resp
868
985
  @app.command("list-knowledge")
869
986
  @handle_api_error
870
987
  def list_knowledge_top_cmd():
@@ -878,6 +995,7 @@ def list_knowledge_top_cmd():
878
995
  table.add_row(item.get("id"), item.get("title") or item.get("name"))
879
996
  console.print(table)
880
997
 
998
+ return resp
881
999
  @app.command("attach")
882
1000
  @handle_api_error
883
1001
  def attach_cmd(
@@ -895,6 +1013,7 @@ def attach_cmd(
895
1013
  full_prompt = f"{prompt}\n\nATTACHMENT: \"{url}\""
896
1014
  create_session_cmd(prompt=full_prompt, file=None, title=title, org=None, max_acu=None,
897
1015
  advanced_mode=None, force=False, wait=wait, interval=interval)
1016
+ return None
898
1017
 
899
1018
  @app.command("update-tags")
900
1019
  @handle_api_error
@@ -907,6 +1026,8 @@ def update_tags_cmd(
907
1026
  sessions.update_session_tags(sid, tags)
908
1027
  console.print(f"[green]Tags updated for session {sid}.[/green]")
909
1028
 
1029
+ return None
1030
+
910
1031
  @app.command("history")
911
1032
  def history_cmd():
912
1033
  """Show the locally cached current session ID."""
@@ -915,6 +1036,9 @@ def history_cmd():
915
1036
  console.print(f"Current local session: [cyan]{sid}[/cyan]")
916
1037
  else:
917
1038
  console.print("No current local session.")
1039
+ return {"current_session_id": sid}
1040
+
1041
+ return {"current_session_id": sid}
918
1042
 
919
1043
  @app.command("messages")
920
1044
  @handle_api_error
@@ -932,6 +1056,8 @@ def messages_cmd(
932
1056
  content = m.get("message", "") or m.get("content", "")
933
1057
  console.print(f"[bold cyan]{role}:[/bold cyan] {content}")
934
1058
 
1059
+ return resp
1060
+
935
1061
  @app.command("get-session")
936
1062
  @handle_api_error
937
1063
  def get_session_top_cmd(
@@ -943,12 +1069,14 @@ def get_session_top_cmd(
943
1069
  console.print(Panel(
944
1070
  f"[bold]Status:[/bold] {resp.get('status_enum')}\n"
945
1071
  f"[bold]URL:[/bold] {resp.get('url')}\n"
946
- f"[bold]Created:[/bold] {resp.get('created_at')}",
1072
+ f"Created: {resp.get('created_at')}",
947
1073
  title=f"Session {sid}"
948
1074
  ))
949
1075
  if "structured_output" in resp:
950
1076
  console.print("[bold]Structured Output:[/bold]")
951
1077
  console.print(json.dumps(resp["structured_output"], indent=2))
1078
+
1079
+ return resp
952
1080
 
953
1081
  @app.command("update-knowledge")
954
1082
  @handle_api_error
@@ -961,6 +1089,15 @@ def update_knowledge_cmd(
961
1089
  """Update an existing knowledge entry."""
962
1090
  knowledge.update_knowledge(knowledge_id, title=name, body=body, trigger=trigger)
963
1091
  console.print(f"[green]Knowledge {knowledge_id} updated.[/green]")
1092
+ return None
1093
+
1094
+ @app.command("get-knowledge")
1095
+ @handle_api_error
1096
+ def get_knowledge_top_cmd(knowledge_id: str = typer.Argument(...)):
1097
+ """Get content of a knowledge note (alias for: devin knowledge get)."""
1098
+ resp = knowledge.get_knowledge(knowledge_id)
1099
+ console.print(Panel(json.dumps(resp, indent=2), title=f"Knowledge: {knowledge_id}"))
1100
+ return resp
964
1101
 
965
1102
  @app.command("update-playbook")
966
1103
  @handle_api_error
@@ -981,8 +1118,9 @@ def update_playbook_top_cmd(
981
1118
  if not data:
982
1119
  console.print("[yellow]Nothing to update. Pass --title, --body, or --macro.[/yellow]")
983
1120
  return
984
- playbooks.update_playbook(playbook_id, title=title or "", body=body, macro=macro)
1121
+ playbooks.update_playbook(playbook_id, title=title, body=body, macro=macro)
985
1122
  console.print(f"[green]Playbook {playbook_id} updated.[/green]")
1123
+ return None
986
1124
 
987
1125
  @app.command("delete-playbook")
988
1126
  @handle_api_error
@@ -991,6 +1129,7 @@ def delete_playbook_top_cmd(playbook_id: str = typer.Argument(...)):
991
1129
  if typer.confirm(f"Delete playbook {playbook_id}?"):
992
1130
  playbooks.delete_playbook(playbook_id)
993
1131
  console.print(f"[green]Playbook {playbook_id} deleted.[/green]")
1132
+ return None
994
1133
 
995
1134
  @app.command("list-secrets")
996
1135
  @handle_api_error
@@ -1006,6 +1145,7 @@ def list_secrets_top_cmd():
1006
1145
  table.add_row(item.get("id", ""), item.get("name", ""))
1007
1146
  console.print(table)
1008
1147
 
1148
+ return resp
1009
1149
  @app.command("delete-secret")
1010
1150
  @handle_api_error
1011
1151
  def delete_secret_top_cmd(secret_id: str = typer.Argument(...)):
@@ -1013,6 +1153,7 @@ def delete_secret_top_cmd(secret_id: str = typer.Argument(...)):
1013
1153
  if typer.confirm(f"Delete secret {secret_id}?"):
1014
1154
  secrets.delete_secret(secret_id)
1015
1155
  console.print(f"[green]Secret {secret_id} deleted.[/green]")
1156
+ return None
1016
1157
 
1017
1158
  @app.command("chain")
1018
1159
  @handle_api_error
@@ -1072,13 +1213,14 @@ def chain_cmd(
1072
1213
  while True:
1073
1214
  resp = sessions.get_session(current_sid)
1074
1215
  status = resp.get("status_enum", "")
1075
- if status in {"blocked", "finished", "stopped", "error"}:
1216
+ if status in {"finished", "stopped", "error"}:
1076
1217
  console.print(f"Step {i+1} done (status: {status}).")
1077
1218
  break
1078
1219
  time.sleep(min(backoff, 10))
1079
1220
  backoff = min(backoff * 1.5, 10)
1080
1221
 
1081
1222
  console.print("[bold green]Chain completed![/bold green]")
1223
+ return resp
1082
1224
 
1083
1225
  if __name__ == "__main__":
1084
1226
  app()
@@ -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