devin-cli 1.3.0__tar.gz → 1.3.2__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.3.0 → devin_cli-1.3.2}/CHANGELOG.md +15 -0
  2. {devin_cli-1.3.0 → devin_cli-1.3.2}/PKG-INFO +3 -1
  3. {devin_cli-1.3.0 → devin_cli-1.3.2}/README.md +2 -0
  4. {devin_cli-1.3.0 → devin_cli-1.3.2}/pyproject.toml +1 -1
  5. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v1/knowledge.py +3 -0
  6. {devin_cli-1.3.0/src/devin_cli/api/v1 → devin_cli-1.3.2/src/devin_cli/api/v3}/secrets.py +3 -0
  7. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v3/sessions.py +13 -2
  8. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/cli.py +103 -12
  9. devin_cli-1.3.0/patch_cli.py +0 -78
  10. devin_cli-1.3.0/test_json.py +0 -46
  11. devin_cli-1.3.0/test_json_wrapper.py +0 -47
  12. {devin_cli-1.3.0 → devin_cli-1.3.2}/.github/workflows/pypi-publish.yml +0 -0
  13. {devin_cli-1.3.0 → devin_cli-1.3.2}/.gitignore +0 -0
  14. {devin_cli-1.3.0 → devin_cli-1.3.2}/LICENSE +0 -0
  15. {devin_cli-1.3.0 → devin_cli-1.3.2}/assets/logo.png +0 -0
  16. {devin_cli-1.3.0 → devin_cli-1.3.2}/generate_facades.py +0 -0
  17. {devin_cli-1.3.0 → devin_cli-1.3.2}/setup.py +0 -0
  18. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/__init__.py +0 -0
  19. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/__init__.py +0 -0
  20. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/attachments.py +0 -0
  21. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/client.py +0 -0
  22. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/consumption.py +0 -0
  23. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/knowledge.py +0 -0
  24. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/members.py +0 -0
  25. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/organizations.py +0 -0
  26. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/playbooks.py +0 -0
  27. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/repositories.py +0 -0
  28. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/schedules.py +0 -0
  29. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/secrets.py +0 -0
  30. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/sessions.py +0 -0
  31. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v1/__init__.py +0 -0
  32. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v1/attachments.py +0 -0
  33. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v1/playbooks.py +0 -0
  34. {devin_cli-1.3.0/src/devin_cli/api/v3 → devin_cli-1.3.2/src/devin_cli/api/v1}/secrets.py +0 -0
  35. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v1/sessions.py +0 -0
  36. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v3/__init__.py +0 -0
  37. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v3/attachments.py +0 -0
  38. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v3/consumption.py +0 -0
  39. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v3/knowledge.py +0 -0
  40. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v3/members.py +0 -0
  41. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v3/organizations.py +0 -0
  42. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v3/playbooks.py +0 -0
  43. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v3/repositories.py +0 -0
  44. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/api/v3/schedules.py +0 -0
  45. {devin_cli-1.3.0 → devin_cli-1.3.2}/src/devin_cli/config.py +0 -0
  46. {devin_cli-1.3.0 → devin_cli-1.3.2}/tests/__init__.py +0 -0
  47. {devin_cli-1.3.0 → devin_cli-1.3.2}/tests/conftest.py +0 -0
  48. {devin_cli-1.3.0 → devin_cli-1.3.2}/tests/test_api/test_attachments.py +0 -0
  49. {devin_cli-1.3.0 → devin_cli-1.3.2}/tests/test_api/test_knowledge.py +0 -0
  50. {devin_cli-1.3.0 → devin_cli-1.3.2}/tests/test_api/test_playbooks.py +0 -0
  51. {devin_cli-1.3.0 → devin_cli-1.3.2}/tests/test_api/test_repositories.py +0 -0
  52. {devin_cli-1.3.0 → devin_cli-1.3.2}/tests/test_api/test_schedules.py +0 -0
  53. {devin_cli-1.3.0 → devin_cli-1.3.2}/tests/test_api/test_secrets.py +0 -0
  54. {devin_cli-1.3.0 → devin_cli-1.3.2}/tests/test_api/test_sessions.py +0 -0
  55. {devin_cli-1.3.0 → devin_cli-1.3.2}/tests/test_cli.py +0 -0
  56. {devin_cli-1.3.0 → devin_cli-1.3.2}/tests/test_config.py +0 -0
@@ -5,6 +5,21 @@ 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.2] - 2026-03-23
9
+ ### Fixed
10
+ - **Service Token 403s**: `sessions get` and `sessions cost` now seamlessly fall back to the `list` endpoint with a `session_ids` filter to cleanly bypass 403 Forbidden errors for `cog_` Service Tokens.
11
+ - **Empty List Tables**: Fixed a core bug where `sessions list` displayed an empty table on v3 profiles by accurately parsing the returning `items` API key.
12
+ - **Terminate Hang**: Solved an architectural issue where `sessions terminate` would hang indefinitely on a 403 by shifting to the correct POST `/terminate` endpoint and strictly enforcing a 5-second `httpx` timeout.
13
+
14
+ ## [1.3.1] - 2026-03-16
15
+ - **Global `--json` Output**: New orchestration flag that converts *all* CLI output and API errors into pure, parseable JSON for AI agents.
16
+ - **Knowledge Get Command**: Added `devin knowledge get <id>` and `devin get-knowledge <id>` to retrieve full note content.
17
+ - Missing v1 and v3 `get_knowledge` / `create_secret` cross-compatibility polyfills.
18
+
19
+ ### Fixed
20
+ - **Audit Fixes**: Resolved 13 critical bugs found during full codebase audit (indentation errors, missing returns, duplicate imports, and terminal status handling).
21
+ - **Warning Suppression**: Suppressed `urllib3` SSL warnings in JSON mode to ensure clean stdout streams.
22
+
8
23
  ## [1.2.0] - 2026-03-11
9
24
  ### Added
10
25
  - `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.3.0
3
+ Version: 1.3.2
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
@@ -153,6 +153,8 @@ Profiles are stored in `~/.config/devin/config.json` — fully isolated includin
153
153
  | `insights` | `[session_id]` | ACU / performance insights (v3) |
154
154
  | `cost` | `[session_id]` | ACU consumption |
155
155
 
156
+ > **Note for Service Tokens (`cog_`):** When retrieving a specific session via `devin sessions get <id>`, the CLI automatically falls back to filtering the org-wide `sessions list` via a `?session_ids=[<id>]` query parameter to gracefully bypass the `403 Forbidden` error natively returned by the direct API endpoint for service accounts.
157
+
156
158
  #### `sessions create` / `create-session` — Full Flag Reference
157
159
 
158
160
  | Flag | Type | Description |
@@ -117,6 +117,8 @@ Profiles are stored in `~/.config/devin/config.json` — fully isolated includin
117
117
  | `insights` | `[session_id]` | ACU / performance insights (v3) |
118
118
  | `cost` | `[session_id]` | ACU consumption |
119
119
 
120
+ > **Note for Service Tokens (`cog_`):** When retrieving a specific session via `devin sessions get <id>`, the CLI automatically falls back to filtering the org-wide `sessions list` via a `?session_ids=[<id>]` query parameter to gracefully bypass the `403 Forbidden` error natively returned by the direct API endpoint for service accounts.
121
+
120
122
  #### `sessions create` / `create-session` — Full Flag Reference
121
123
 
122
124
  | Flag | Type | Description |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devin-cli"
3
- version = "1.3.0"
3
+ version = "1.3.2"
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}")
@@ -62,7 +62,18 @@ def create_session(
62
62
  return client.post("sessions", json=data)
63
63
 
64
64
  def get_session(session_id: str):
65
- return client.get(f"sessions/{session_id}")
65
+ try:
66
+ return client.get(f"sessions/{session_id}")
67
+ except Exception as e:
68
+ from devin_cli.api.client import APIError
69
+ if isinstance(e, APIError) and e.status_code in (403, 404):
70
+ # Fallback to list endpoint with session_ids filter (especially for cog_ tokens)
71
+ res = client.get("sessions", params={"session_ids": [session_id]})
72
+ items = res.get("items", [])
73
+ if items:
74
+ return items[0]
75
+ raise
76
+ raise
66
77
 
67
78
  def get_session_messages(session_id: str):
68
79
  return client.get(f"sessions/{session_id}/messages")
@@ -83,7 +94,7 @@ def append_session_tags(session_id: str, tags: List[str]):
83
94
  return client.post(f"sessions/{session_id}/tags", json={"tags": tags})
84
95
 
85
96
  def terminate_session(session_id: str):
86
- return client.delete(f"sessions/{session_id}")
97
+ return client.post(f"sessions/{session_id}/terminate", timeout=5.0)
87
98
 
88
99
  def archive_session(session_id: str):
89
100
  return client.post(f"sessions/{session_id}/archive")
@@ -1,3 +1,12 @@
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
@@ -16,7 +25,6 @@ from devin_cli.config import config
16
25
  from devin_cli.api import sessions, knowledge, playbooks, secrets, attachments, repositories, schedules, organizations, members, consumption
17
26
  from devin_cli.api.client import client, APIError
18
27
  import webbrowser
19
- import functools
20
28
  import sys
21
29
  import hashlib
22
30
  import importlib.metadata
@@ -88,10 +96,7 @@ def main(
88
96
  Unofficial CLI for Devin AI v3.
89
97
  """
90
98
  if json_format:
91
- import os
92
- import warnings
93
99
  os.environ["DEVIN_OUTPUT_FORMAT"] = "json"
94
- warnings.filterwarnings("ignore")
95
100
 
96
101
  if version:
97
102
  console.print(f"devin CLI version: {__version__}")
@@ -188,6 +193,7 @@ def configure(
188
193
  if org_id:
189
194
  config.org_id = org_id
190
195
  console.print(f"[green]Configuration saved to {config.config_file}[/green]")
196
+ return None
191
197
 
192
198
  # --- Sessions ---
193
199
  @session_app.command("create")
@@ -303,6 +309,8 @@ def create_session_cmd(
303
309
  break
304
310
  else:
305
311
  console.print("[yellow]Session created, but no ID returned immediately (awaiting advanced mode setup).[/yellow]")
312
+
313
+ return resp
306
314
 
307
315
 
308
316
  @session_app.command("list")
@@ -315,7 +323,7 @@ def list_sessions_cmd(
315
323
  """List sessions."""
316
324
  if org: config.temporary_org_id = org
317
325
  resp = sessions.list_sessions(limit=limit)
318
- sess_list = resp.get("sessions", [])
326
+ sess_list = resp.get("items", resp.get("sessions", []))
319
327
 
320
328
  if not sess_list and config.api_token and config.api_token.startswith("cog_"):
321
329
  console.print("[yellow]Warning: Service tokens (cog_) may only have visibility into sessions they explicitly created, not all organization sessions.[/yellow]")
@@ -330,6 +338,8 @@ def list_sessions_cmd(
330
338
  for s in sess_list:
331
339
  table.add_row(s.get("session_id"), s.get("status_enum"), s.get("title") or s.get("prompt", "")[:50])
332
340
  console.print(table)
341
+
342
+ return resp
333
343
 
334
344
  @session_app.command("get")
335
345
  @handle_api_error
@@ -342,6 +352,8 @@ def get_session_cmd(
342
352
  sid = session_id or get_current_session_id()
343
353
  resp = sessions.get_session(sid)
344
354
  console.print(Panel(json.dumps(resp, indent=2), title=f"Session {sid}"))
355
+
356
+ return resp
345
357
 
346
358
  @session_app.command("insights")
347
359
  @handle_api_error
@@ -358,6 +370,8 @@ def session_insights_cmd(
358
370
  console.print("Tip: Switch to v3 with [bold cyan]devin configure[/bold cyan] or use [bold cyan]--profile [v3-profile][/bold cyan]")
359
371
  else:
360
372
  console.print(Panel(json.dumps(resp, indent=2), title=f"Insights for {sid}"))
373
+
374
+ return resp
361
375
 
362
376
  @session_app.command("cost")
363
377
  @handle_api_error
@@ -381,6 +395,8 @@ def session_cost_cmd(
381
395
  else:
382
396
  resp = consumption.get_daily_consumption_breakdown()
383
397
  console.print(Panel(json.dumps(resp, indent=2), title="Daily Consumption"))
398
+
399
+ return resp
384
400
 
385
401
  @session_app.command("message")
386
402
  @handle_api_error
@@ -405,6 +421,7 @@ def send_message_cmd(
405
421
  raise typer.Exit(1)
406
422
  sessions.send_message(sid, msg_text)
407
423
  console.print(f"[green]Message sent to {sid}[/green]")
424
+ return None
408
425
 
409
426
  @session_app.command("messages")
410
427
  @handle_api_error
@@ -422,6 +439,8 @@ def list_messages_cmd(
422
439
  content = m.get("message", "") or m.get("content", "")
423
440
  console.print(f"[bold cyan]{role}:[/bold cyan] {content}")
424
441
  console.print("---")
442
+
443
+ return resp
425
444
 
426
445
  @session_app.command("terminate")
427
446
  @handle_api_error
@@ -435,6 +454,7 @@ def terminate_session_cmd(
435
454
  if typer.confirm(f"Terminate session {sid}?"):
436
455
  sessions.terminate_session(sid)
437
456
  console.print(f"[green]Session {sid} terminated.[/green]")
457
+ return None
438
458
 
439
459
  # --- Knowledge ---
440
460
  @knowledge_app.command("list")
@@ -464,6 +484,21 @@ def create_knowledge_cmd(
464
484
  if org: config.temporary_org_id = org
465
485
  resp = knowledge.create_knowledge(title=title, body=body, trigger=trigger)
466
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
467
502
 
468
503
  @knowledge_app.command("delete")
469
504
  @handle_api_error
@@ -476,6 +511,8 @@ def delete_knowledge_cmd(
476
511
  if typer.confirm(f"Delete knowledge {knowledge_id}?"):
477
512
  knowledge.delete_knowledge(knowledge_id)
478
513
  console.print(f"[green]Knowledge {knowledge_id} deleted.[/green]")
514
+
515
+ return None
479
516
 
480
517
  # --- Playbooks ---
481
518
  @playbook_app.command("list")
@@ -500,6 +537,7 @@ def list_playbooks_cmd(
500
537
  if isinstance(item, dict):
501
538
  table.add_row(item.get("playbook_id", ""), item.get("title", ""))
502
539
  console.print(table)
540
+ return resp
503
541
 
504
542
  @playbook_app.command("update")
505
543
  @handle_api_error
@@ -520,6 +558,8 @@ def update_playbook_cmd(
520
558
  if org: config.temporary_org_id = org
521
559
  resp = playbooks.update_playbook(playbook_id, title=title, body=body, macro=macro)
522
560
  console.print(f"[green]Playbook updated:[/green] {playbook_id}")
561
+
562
+ return resp
523
563
 
524
564
  @playbook_app.command("create")
525
565
  @handle_api_error
@@ -539,6 +579,8 @@ def create_playbook_cmd(
539
579
  if org: config.temporary_org_id = org
540
580
  resp = playbooks.create_playbook(title, body, macro)
541
581
  console.print(f"[green]Playbook created:[/green] {resp.get('playbook_id')}")
582
+
583
+ return resp
542
584
 
543
585
  @playbook_app.command("delete")
544
586
  @handle_api_error
@@ -551,6 +593,7 @@ def delete_playbook_cmd(
551
593
  if typer.confirm(f"Delete playbook {playbook_id}?"):
552
594
  playbooks.delete_playbook(playbook_id)
553
595
  console.print(f"[green]Playbook {playbook_id} deleted.[/green]")
596
+ return None
554
597
 
555
598
  # --- Secrets ---
556
599
  @secret_app.command("list")
@@ -579,6 +622,7 @@ def list_secrets_cmd(
579
622
  if isinstance(item, dict):
580
623
  table.add_row(item.get("id"), item.get("name"))
581
624
  console.print(table)
625
+ return resp
582
626
 
583
627
  @secret_app.command("delete")
584
628
  @handle_api_error
@@ -591,6 +635,7 @@ def delete_secret_cmd(
591
635
  if typer.confirm(f"Delete secret {secret_id}?"):
592
636
  secrets.delete_secret(secret_id)
593
637
  console.print(f"[green]Secret {secret_id} deleted.[/green]")
638
+ return None
594
639
 
595
640
  @secret_app.command("create")
596
641
  @handle_api_error
@@ -603,6 +648,8 @@ def create_secret_cmd(
603
648
  if org: config.temporary_org_id = org
604
649
  secrets.create_secret(name, value)
605
650
  console.print(f"[green]Secret '{name}' created.[/green]")
651
+
652
+ return None
606
653
 
607
654
  # --- Schedules ---
608
655
  @schedule_app.command("list")
@@ -619,6 +666,7 @@ def list_schedules_cmd(org: Optional[str] = typer.Option(None, "--org")):
619
666
  for item in items:
620
667
  table.add_row(item.get("id"), item.get("title"), item.get("cron"))
621
668
  console.print(table)
669
+
622
670
 
623
671
  return resp
624
672
  @schedule_app.command("create")
@@ -634,6 +682,8 @@ def create_schedule_cmd(
634
682
  resp = schedules.create_schedule(prompt=prompt, cron=cron, title=title)
635
683
  console.print(f"[green]Created schedule:[/green] {resp.get('id')}")
636
684
 
685
+ return resp
686
+
637
687
  # --- Repositories ---
638
688
  @repo_app.command("list")
639
689
  @handle_api_error
@@ -674,6 +724,7 @@ def list_repos_cmd(
674
724
  )
675
725
  table.add_row(path, "Yes" if indexed else "No")
676
726
  console.print(table)
727
+ return resp
677
728
 
678
729
  @repo_app.command("index")
679
730
  @handle_api_error
@@ -686,6 +737,8 @@ def index_repo_cmd(
686
737
  repositories.index_repository(path)
687
738
  console.print(f"[green]Indexing started for {path}[/green]")
688
739
 
740
+ return None
741
+
689
742
  # --- Attachments ---
690
743
  @attachment_app.command("upload")
691
744
  @handle_api_error
@@ -697,6 +750,7 @@ def upload_attachment_cmd(
697
750
  if org: config.temporary_org_id = org
698
751
  resp = attachments.upload_file(str(path))
699
752
  console.print(f"[green]Uploaded:[/green] {resp}")
753
+ return resp
700
754
 
701
755
  @attachment_app.command("download")
702
756
  @handle_api_error
@@ -713,6 +767,8 @@ def download_attachment_cmd(
713
767
  out_path.write_bytes(content)
714
768
  console.print(f"[green]Downloaded to:[/green] {out_path}")
715
769
 
770
+ return None
771
+
716
772
  # --- Enterprise Discovery ---
717
773
  @enterprise_app.command("whoami")
718
774
  @handle_api_error
@@ -736,6 +792,7 @@ def use_session_cmd(session_id: str):
736
792
  """Switch the current active session."""
737
793
  config.current_session_id = session_id
738
794
  console.print(f"[green]Switched to session {session_id}[/green]")
795
+ return {"current_session_id": session_id}
739
796
 
740
797
  @app.command("status")
741
798
  @handle_api_error
@@ -758,7 +815,7 @@ def status_cmd():
758
815
  if url:
759
816
  console.print(f"[bold]URL:[/bold] [underline]{url}[/underline]")
760
817
 
761
- return resp
818
+ return resp
762
819
  @app.command("open")
763
820
  @handle_api_error
764
821
  def open_cmd(
@@ -774,6 +831,8 @@ def open_cmd(
774
831
  webbrowser.open(url)
775
832
  console.print(f"[green]Opened:[/green] {url}")
776
833
 
834
+ return resp
835
+
777
836
  @session_app.command("watch")
778
837
  @handle_api_error
779
838
  def watch_session_cmd(
@@ -807,7 +866,6 @@ def watch_session_cmd(
807
866
  text.append("\nStructured Output:\n", style="bold")
808
867
  text.append(json.dumps(so, indent=2))
809
868
  text.append("\nPress Ctrl+C to stop watching.", style="dim")
810
- return Panel(text, title="[bold cyan]Devin Watch[/bold cyan]", border_style="cyan")
811
869
 
812
870
  console.print(f"[bold cyan]Watching session {sid}[/bold cyan] (Ctrl+C to stop)")
813
871
 
@@ -828,6 +886,7 @@ def watch_session_cmd(
828
886
  return
829
887
 
830
888
  console.print(f"[bold green]Session {resp.get('status_enum')}![/bold green]")
889
+ return resp
831
890
 
832
891
  # --- Flat top-level aliases (backward compat with 0.1.x command structure) ---
833
892
 
@@ -839,6 +898,7 @@ def watch_cmd(
839
898
  ):
840
899
  """Alias for: devin sessions watch"""
841
900
  watch_session_cmd(session_id=session_id, interval=interval, org=None)
901
+ return None
842
902
 
843
903
  @app.command("message")
844
904
  @handle_api_error
@@ -861,6 +921,7 @@ def message_cmd(
861
921
  raise typer.Exit(1)
862
922
  sessions.send_message(sid, msg_text)
863
923
  console.print(f"[green]Message sent to {sid}[/green]")
924
+ return None
864
925
 
865
926
  @app.command("terminate")
866
927
  @handle_api_error
@@ -872,6 +933,7 @@ def terminate_cmd(
872
933
  if typer.confirm(f"Terminate session {sid}?"):
873
934
  sessions.terminate_session(sid)
874
935
  console.print(f"[green]Session {sid} terminated.[/green]")
936
+ return None
875
937
 
876
938
  @app.command("list-sessions")
877
939
  @handle_api_error
@@ -881,7 +943,7 @@ def list_sessions_top_cmd(
881
943
  ):
882
944
  """List recent sessions (alias for: devin sessions list)."""
883
945
  resp = sessions.list_sessions(limit=limit)
884
- sess_list = resp.get("sessions", [])
946
+ sess_list = resp.get("items", resp.get("sessions", []))
885
947
  if json_output:
886
948
  console.print(json.dumps(sess_list, indent=2))
887
949
  return
@@ -892,6 +954,7 @@ def list_sessions_top_cmd(
892
954
  for s in sess_list:
893
955
  table.add_row(s.get("session_id"), s.get("status_enum"), s.get("title") or s.get("prompt", "")[:50])
894
956
  console.print(table)
957
+ return resp
895
958
 
896
959
  @app.command("create-session")
897
960
  @handle_api_error
@@ -904,7 +967,12 @@ def create_session_top_cmd(
904
967
  ):
905
968
  """Create a new session (alias for: devin sessions create)."""
906
969
  create_session_cmd(prompt=prompt, file=None, title=title, org=None, max_acu=None,
907
- 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
908
976
 
909
977
  @app.command("upload")
910
978
  @handle_api_error
@@ -945,6 +1013,7 @@ def attach_cmd(
945
1013
  full_prompt = f"{prompt}\n\nATTACHMENT: \"{url}\""
946
1014
  create_session_cmd(prompt=full_prompt, file=None, title=title, org=None, max_acu=None,
947
1015
  advanced_mode=None, force=False, wait=wait, interval=interval)
1016
+ return None
948
1017
 
949
1018
  @app.command("update-tags")
950
1019
  @handle_api_error
@@ -957,6 +1026,8 @@ def update_tags_cmd(
957
1026
  sessions.update_session_tags(sid, tags)
958
1027
  console.print(f"[green]Tags updated for session {sid}.[/green]")
959
1028
 
1029
+ return None
1030
+
960
1031
  @app.command("history")
961
1032
  def history_cmd():
962
1033
  """Show the locally cached current session ID."""
@@ -965,6 +1036,9 @@ def history_cmd():
965
1036
  console.print(f"Current local session: [cyan]{sid}[/cyan]")
966
1037
  else:
967
1038
  console.print("No current local session.")
1039
+ return {"current_session_id": sid}
1040
+
1041
+ return {"current_session_id": sid}
968
1042
 
969
1043
  @app.command("messages")
970
1044
  @handle_api_error
@@ -982,6 +1056,8 @@ def messages_cmd(
982
1056
  content = m.get("message", "") or m.get("content", "")
983
1057
  console.print(f"[bold cyan]{role}:[/bold cyan] {content}")
984
1058
 
1059
+ return resp
1060
+
985
1061
  @app.command("get-session")
986
1062
  @handle_api_error
987
1063
  def get_session_top_cmd(
@@ -993,12 +1069,14 @@ def get_session_top_cmd(
993
1069
  console.print(Panel(
994
1070
  f"[bold]Status:[/bold] {resp.get('status_enum')}\n"
995
1071
  f"[bold]URL:[/bold] {resp.get('url')}\n"
996
- f"[bold]Created:[/bold] {resp.get('created_at')}",
1072
+ f"Created: {resp.get('created_at')}",
997
1073
  title=f"Session {sid}"
998
1074
  ))
999
1075
  if "structured_output" in resp:
1000
1076
  console.print("[bold]Structured Output:[/bold]")
1001
1077
  console.print(json.dumps(resp["structured_output"], indent=2))
1078
+
1079
+ return resp
1002
1080
 
1003
1081
  @app.command("update-knowledge")
1004
1082
  @handle_api_error
@@ -1011,6 +1089,15 @@ def update_knowledge_cmd(
1011
1089
  """Update an existing knowledge entry."""
1012
1090
  knowledge.update_knowledge(knowledge_id, title=name, body=body, trigger=trigger)
1013
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
1014
1101
 
1015
1102
  @app.command("update-playbook")
1016
1103
  @handle_api_error
@@ -1031,8 +1118,9 @@ def update_playbook_top_cmd(
1031
1118
  if not data:
1032
1119
  console.print("[yellow]Nothing to update. Pass --title, --body, or --macro.[/yellow]")
1033
1120
  return
1034
- playbooks.update_playbook(playbook_id, title=title or "", body=body, macro=macro)
1121
+ playbooks.update_playbook(playbook_id, title=title, body=body, macro=macro)
1035
1122
  console.print(f"[green]Playbook {playbook_id} updated.[/green]")
1123
+ return None
1036
1124
 
1037
1125
  @app.command("delete-playbook")
1038
1126
  @handle_api_error
@@ -1041,6 +1129,7 @@ def delete_playbook_top_cmd(playbook_id: str = typer.Argument(...)):
1041
1129
  if typer.confirm(f"Delete playbook {playbook_id}?"):
1042
1130
  playbooks.delete_playbook(playbook_id)
1043
1131
  console.print(f"[green]Playbook {playbook_id} deleted.[/green]")
1132
+ return None
1044
1133
 
1045
1134
  @app.command("list-secrets")
1046
1135
  @handle_api_error
@@ -1064,6 +1153,7 @@ def delete_secret_top_cmd(secret_id: str = typer.Argument(...)):
1064
1153
  if typer.confirm(f"Delete secret {secret_id}?"):
1065
1154
  secrets.delete_secret(secret_id)
1066
1155
  console.print(f"[green]Secret {secret_id} deleted.[/green]")
1156
+ return None
1067
1157
 
1068
1158
  @app.command("chain")
1069
1159
  @handle_api_error
@@ -1123,13 +1213,14 @@ def chain_cmd(
1123
1213
  while True:
1124
1214
  resp = sessions.get_session(current_sid)
1125
1215
  status = resp.get("status_enum", "")
1126
- if status in {"blocked", "finished", "stopped", "error"}:
1216
+ if status in {"finished", "stopped", "error"}:
1127
1217
  console.print(f"Step {i+1} done (status: {status}).")
1128
1218
  break
1129
1219
  time.sleep(min(backoff, 10))
1130
1220
  backoff = min(backoff * 1.5, 10)
1131
1221
 
1132
1222
  console.print("[bold green]Chain completed![/bold green]")
1223
+ return resp
1133
1224
 
1134
1225
  if __name__ == "__main__":
1135
1226
  app()
@@ -1,78 +0,0 @@
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,46 +0,0 @@
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()
@@ -1,47 +0,0 @@
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()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes