devin-cli 1.1.8__tar.gz → 1.2.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 (53) hide show
  1. {devin_cli-1.1.8 → devin_cli-1.2.0}/CHANGELOG.md +15 -0
  2. {devin_cli-1.1.8 → devin_cli-1.2.0}/PKG-INFO +1 -1
  3. {devin_cli-1.1.8 → devin_cli-1.2.0}/pyproject.toml +1 -1
  4. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v1/sessions.py +12 -8
  5. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/cli.py +86 -15
  6. {devin_cli-1.1.8 → devin_cli-1.2.0}/.github/workflows/pypi-publish.yml +0 -0
  7. {devin_cli-1.1.8 → devin_cli-1.2.0}/.gitignore +0 -0
  8. {devin_cli-1.1.8 → devin_cli-1.2.0}/LICENSE +0 -0
  9. {devin_cli-1.1.8 → devin_cli-1.2.0}/README.md +0 -0
  10. {devin_cli-1.1.8 → devin_cli-1.2.0}/assets/logo.png +0 -0
  11. {devin_cli-1.1.8 → devin_cli-1.2.0}/generate_facades.py +0 -0
  12. {devin_cli-1.1.8 → devin_cli-1.2.0}/setup.py +0 -0
  13. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/__init__.py +0 -0
  14. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/__init__.py +0 -0
  15. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/attachments.py +0 -0
  16. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/client.py +0 -0
  17. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/consumption.py +0 -0
  18. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/knowledge.py +0 -0
  19. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/members.py +0 -0
  20. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/organizations.py +0 -0
  21. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/playbooks.py +0 -0
  22. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/repositories.py +0 -0
  23. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/schedules.py +0 -0
  24. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/secrets.py +0 -0
  25. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/sessions.py +0 -0
  26. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v1/__init__.py +0 -0
  27. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v1/attachments.py +0 -0
  28. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v1/knowledge.py +0 -0
  29. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v1/playbooks.py +0 -0
  30. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v1/secrets.py +0 -0
  31. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v3/__init__.py +0 -0
  32. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v3/attachments.py +0 -0
  33. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v3/consumption.py +0 -0
  34. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v3/knowledge.py +0 -0
  35. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v3/members.py +0 -0
  36. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v3/organizations.py +0 -0
  37. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v3/playbooks.py +0 -0
  38. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v3/repositories.py +0 -0
  39. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v3/schedules.py +0 -0
  40. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v3/secrets.py +0 -0
  41. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/api/v3/sessions.py +0 -0
  42. {devin_cli-1.1.8 → devin_cli-1.2.0}/src/devin_cli/config.py +0 -0
  43. {devin_cli-1.1.8 → devin_cli-1.2.0}/tests/__init__.py +0 -0
  44. {devin_cli-1.1.8 → devin_cli-1.2.0}/tests/conftest.py +0 -0
  45. {devin_cli-1.1.8 → devin_cli-1.2.0}/tests/test_api/test_attachments.py +0 -0
  46. {devin_cli-1.1.8 → devin_cli-1.2.0}/tests/test_api/test_knowledge.py +0 -0
  47. {devin_cli-1.1.8 → devin_cli-1.2.0}/tests/test_api/test_playbooks.py +0 -0
  48. {devin_cli-1.1.8 → devin_cli-1.2.0}/tests/test_api/test_repositories.py +0 -0
  49. {devin_cli-1.1.8 → devin_cli-1.2.0}/tests/test_api/test_schedules.py +0 -0
  50. {devin_cli-1.1.8 → devin_cli-1.2.0}/tests/test_api/test_secrets.py +0 -0
  51. {devin_cli-1.1.8 → devin_cli-1.2.0}/tests/test_api/test_sessions.py +0 -0
  52. {devin_cli-1.1.8 → devin_cli-1.2.0}/tests/test_cli.py +0 -0
  53. {devin_cli-1.1.8 → devin_cli-1.2.0}/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.2.0] - 2026-03-11
9
+ ### Added
10
+ - `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`.
11
+ - Profile feedback on session create: prints which profile and API version is being used.
12
+ - Explicit warning when v3-only flags (`--advanced-mode`, `--playbook-id`, `--repo`, `--secret-id`, `--session-link`, `--attachment-url`, `--create-as-user-id`) are passed on a v1 profile — instead of silently ignored.
13
+ ### Fixed
14
+ - Removed `**kwargs` anti-pattern from `v1/sessions.create_session`. Typos in keyword args now raise `TypeError` instead of silently vanishing.
15
+ - v3-only params added as explicit no-ops in v1 `create_session` so the shared CLI call site works without `TypeError`.
16
+
17
+ ## [1.1.9] - 2026-03-11
18
+ ### Fixed
19
+ - `devin watch` terminal status message was printed inside a `Live` context, causing output corruption. Now printed after the `Live` block exits.
20
+ - `update-playbook` was sending an empty string title when `--title` was not passed, overwriting the existing playbook title. Now only sends fields that are explicitly provided.
21
+ - `chain` used bare `resp["session_id"]` key access which would raise `KeyError` on unexpected API responses. Now uses `.get()` with an explicit error guard.
22
+
8
23
  ## [1.1.8] - 2026-03-11
9
24
  ### Added
10
25
  - Restored all 0.1.x flat commands: `watch`, `status`, `open`, `message`, `terminate`, `list-sessions`, `create-session`, `upload`, `list-knowledge`, `attach`, `update-tags`, `history`, `messages`, `get-session`, `update-knowledge`, `update-playbook`, `delete-playbook`, `list-secrets`, `delete-secret`, `chain`.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devin-cli
3
- Version: 1.1.8
3
+ Version: 1.2.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devin-cli"
3
- version = "1.1.8"
3
+ version = "1.2.0"
4
4
  description = "Unofficial CLI for Devin AI - The first AI Software Engineer"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,7 +1,7 @@
1
1
  from typing import List, Optional
2
2
  from devin_cli.api.client import client
3
3
 
4
- def list_sessions(limit: int = 100, offset: int = 0, tags: List[str] = None):
4
+ def list_sessions(limit: int = 100, offset: int = 0, tags: Optional[List[str]] = None):
5
5
  params = {"limit": limit, "offset": offset}
6
6
  if tags:
7
7
  params["tags"] = tags
@@ -10,21 +10,25 @@ def list_sessions(limit: int = 100, offset: int = 0, tags: List[str] = None):
10
10
  def create_session(
11
11
  prompt: str,
12
12
  idempotent: bool = False,
13
- snapshot_id: str = None,
14
- playbook_id: str = None,
13
+ snapshot_id: Optional[str] = None,
14
+ playbook_id: Optional[str] = None,
15
15
  unlisted: bool = False,
16
- tags: List[str] = None,
17
- session_secrets: List[dict] = None,
16
+ tags: Optional[List[str]] = None,
17
+ session_secrets: Optional[List[dict]] = None,
18
18
  title: Optional[str] = None,
19
19
  knowledge_ids: Optional[List[str]] = None,
20
20
  secret_ids: Optional[List[str]] = None,
21
21
  max_acu_limit: Optional[int] = None,
22
- **kwargs
22
+ advanced_mode: Optional[str] = None,
23
+ repos: Optional[List[str]] = None,
24
+ session_links: Optional[List[str]] = None,
25
+ attachment_urls: Optional[List[str]] = None,
26
+ create_as_user_id: Optional[str] = None,
23
27
  ):
24
28
  data = {
25
29
  "prompt": prompt,
26
30
  "idempotent": idempotent,
27
- "unlisted": unlisted
31
+ "unlisted": unlisted,
28
32
  }
29
33
  if snapshot_id:
30
34
  data["snapshot_id"] = snapshot_id
@@ -42,7 +46,7 @@ def create_session(
42
46
  data["secret_ids"] = secret_ids
43
47
  if max_acu_limit:
44
48
  data["max_acu_limit"] = max_acu_limit
45
-
49
+
46
50
  return client.post("sessions", json=data)
47
51
 
48
52
  def get_session(session_id: str):
@@ -155,13 +155,38 @@ def create_session_cmd(
155
155
  title: Optional[str] = typer.Option(None, "--title", "-t", help="Custom session title"),
156
156
  org: Optional[str] = typer.Option(None, "--org", help="Override organization ID"),
157
157
  max_acu: Optional[int] = typer.Option(None, "--max-acu", help="Maximum ACU limit"),
158
- advanced_mode: Optional[str] = typer.Option(None, "--advanced-mode", help="Enable advanced mode (e.g. CLI, browser) for the session"),
158
+ advanced_mode: Optional[str] = typer.Option(None, "--advanced-mode", help="Advanced mode type: 'cli' or 'browser' (v3 only)"),
159
+ playbook_id: Optional[str] = typer.Option(None, "--playbook-id", help="Playbook ID to apply (v3 only)"),
160
+ tags: Optional[List[str]] = typer.Option(None, "--tag", help="Session tags (repeatable)"),
161
+ repos: Optional[List[str]] = typer.Option(None, "--repo", help="Repository URLs to attach (v3 only, repeatable)"),
162
+ knowledge_ids: Optional[List[str]] = typer.Option(None, "--knowledge-id", help="Knowledge IDs to attach (repeatable)"),
163
+ secret_ids: Optional[List[str]] = typer.Option(None, "--secret-id", help="Secret IDs to inject (v3 only, repeatable)"),
164
+ session_links: Optional[List[str]] = typer.Option(None, "--session-link", help="Session URLs to link as context (v3 only, repeatable)"),
165
+ attachment_urls: Optional[List[str]] = typer.Option(None, "--attachment-url", help="Attachment URLs to attach (v3 only, repeatable)"),
166
+ create_as_user_id: Optional[str] = typer.Option(None, "--create-as-user-id", help="Enterprise: create session on behalf of this user ID (v3 only)"),
159
167
  force: bool = typer.Option(False, "--force", help="Force creation even if duplicate prompt is detected"),
160
168
  wait: bool = typer.Option(False, "--wait", "-w", help="Block until the session reaches a terminal status"),
161
169
  interval: int = typer.Option(5, "--interval", help="Polling interval in seconds when --wait is used"),
162
170
  ):
163
171
  """Create a new Devin session."""
164
172
  if org: config.temporary_org_id = org
173
+
174
+ api_ver = config.api_version
175
+ console.print(f"[dim]Using profile: {config.active_profile or 'default'} ({api_ver})[/dim]")
176
+
177
+ v3_only_used = [x for x, v in [
178
+ ("--advanced-mode", advanced_mode),
179
+ ("--playbook-id", playbook_id),
180
+ ("--repos", repos),
181
+ ("--secret-ids", secret_ids),
182
+ ("--session-links", session_links),
183
+ ("--attachment-urls", attachment_urls),
184
+ ("--create-as-user-id", create_as_user_id),
185
+ ] if v]
186
+ if api_ver == "v1" and v3_only_used:
187
+ console.print(f"[bold yellow]Warning:[/bold yellow] The following flags are v3-only and will be ignored on a v1 profile: {', '.join(v3_only_used)}")
188
+ console.print(" Run [bold cyan]devin configure[/bold cyan] to switch to a v3 profile.")
189
+
165
190
  if file:
166
191
  prompt_text = file.read_text()
167
192
  elif prompt:
@@ -169,10 +194,10 @@ def create_session_cmd(
169
194
  else:
170
195
  console.print("[bold red]Error:[/bold red] Must provide prompt or --file")
171
196
  raise typer.Exit(1)
172
-
197
+
173
198
  prompt_hash = hashlib.sha256(prompt_text.encode('utf-8')).hexdigest()
174
199
  existing_sid = config.get_session_by_prompt_hash(prompt_hash)
175
-
200
+
176
201
  if existing_sid and not force:
177
202
  console.print(f"[bold yellow]Duplicate Detected:[/bold yellow] You recently created a session with this exact prompt.")
178
203
  console.print(f"Existing Session ID: [bold cyan]{existing_sid}[/bold cyan]")
@@ -182,13 +207,21 @@ def create_session_cmd(
182
207
 
183
208
  with console.status("[bold green]Creating session...[/bold green]"):
184
209
  resp = sessions.create_session(
185
- prompt=prompt_text,
186
- title=title,
210
+ prompt=prompt_text,
211
+ title=title,
187
212
  max_acu_limit=max_acu,
188
- advanced_mode=advanced_mode
213
+ advanced_mode=advanced_mode,
214
+ playbook_id=playbook_id,
215
+ tags=tags or None,
216
+ repos=repos or None,
217
+ knowledge_ids=knowledge_ids or None,
218
+ secret_ids=secret_ids or None,
219
+ session_links=session_links or None,
220
+ attachment_urls=attachment_urls or None,
221
+ create_as_user_id=create_as_user_id,
189
222
  )
190
223
  sid = resp.get("session_id")
191
-
224
+
192
225
  if "advanced_mode_url" in resp:
193
226
  adv_url = resp["advanced_mode_url"]
194
227
  console.print(f"[bold yellow]Advanced Mode Authorization Required![/bold yellow]")
@@ -197,7 +230,7 @@ def create_session_cmd(
197
230
  if typer.confirm("Open browser now?"):
198
231
  import webbrowser
199
232
  webbrowser.open(adv_url)
200
-
233
+
201
234
  if sid:
202
235
  config.current_session_id = sid
203
236
  config.save_prompt_hash(prompt_hash, sid)
@@ -219,6 +252,7 @@ def create_session_cmd(
219
252
  else:
220
253
  console.print("[yellow]Session created, but no ID returned immediately (awaiting advanced mode setup).[/yellow]")
221
254
 
255
+
222
256
  @session_app.command("list")
223
257
  @handle_api_error
224
258
  def list_sessions_cmd(
@@ -299,14 +333,25 @@ def session_cost_cmd(
299
333
  @session_app.command("message")
300
334
  @handle_api_error
301
335
  def send_message_cmd(
302
- text: str = typer.Argument(...),
336
+ text: Optional[str] = typer.Argument(None, help="Message text"),
337
+ file: Optional[Path] = typer.Option(None, "--file", "-f", help="Read message from file"),
303
338
  session_id: Optional[str] = typer.Option(None, "--id"),
304
339
  org: Optional[str] = typer.Option(None, "--org"),
305
340
  ):
306
341
  """Send a message to a session."""
307
342
  if org: config.temporary_org_id = org
308
343
  sid = session_id or get_current_session_id()
309
- sessions.send_message(sid, text)
344
+ if file:
345
+ if not file.exists():
346
+ console.print(f"[bold red]Error:[/bold red] File not found: {file}")
347
+ raise typer.Exit(1)
348
+ msg_text = file.read_text()
349
+ elif text:
350
+ msg_text = text
351
+ else:
352
+ console.print("[bold red]Error:[/bold red] Provide message text or --file")
353
+ raise typer.Exit(1)
354
+ sessions.send_message(sid, msg_text)
310
355
  console.print(f"[green]Message sent to {sid}[/green]")
311
356
 
312
357
  @session_app.command("messages")
@@ -708,13 +753,15 @@ def watch_session_cmd(
708
753
 
709
754
  if resp.get("status_enum", "").lower() in terminal_statuses:
710
755
  live.update(build_status_panel(resp))
711
- console.print(f"[bold green]Session {resp.get('status_enum')}![/bold green]")
712
756
  break
713
757
 
714
758
  time.sleep(min(backoff, 30))
715
759
  backoff = min(backoff * 1.5, 30)
716
760
  except KeyboardInterrupt:
717
761
  console.print("\n[dim]Watch stopped.[/dim]")
762
+ return
763
+
764
+ console.print(f"[bold green]Session {resp.get('status_enum')}![/bold green]")
718
765
 
719
766
  # --- Flat top-level aliases (backward compat with 0.1.x command structure) ---
720
767
 
@@ -730,12 +777,23 @@ def watch_cmd(
730
777
  @app.command("message")
731
778
  @handle_api_error
732
779
  def message_cmd(
733
- text: str = typer.Argument(..., help="Message to send"),
780
+ text: Optional[str] = typer.Argument(None, help="Message to send"),
781
+ file: Optional[Path] = typer.Option(None, "--file", "-f", help="Read message from file"),
734
782
  session_id: Optional[str] = typer.Option(None, "--id"),
735
783
  ):
736
784
  """Send a message to the active session."""
737
785
  sid = session_id or get_current_session_id()
738
- sessions.send_message(sid, text)
786
+ if file:
787
+ if not file.exists():
788
+ console.print(f"[bold red]Error:[/bold red] File not found: {file}")
789
+ raise typer.Exit(1)
790
+ msg_text = file.read_text()
791
+ elif text:
792
+ msg_text = text
793
+ else:
794
+ console.print("[bold red]Error:[/bold red] Provide message text or --file")
795
+ raise typer.Exit(1)
796
+ sessions.send_message(sid, msg_text)
739
797
  console.print(f"[green]Message sent to {sid}[/green]")
740
798
 
741
799
  @app.command("terminate")
@@ -895,6 +953,16 @@ def update_playbook_top_cmd(
895
953
  macro: Optional[str] = typer.Option(None, "--macro"),
896
954
  ):
897
955
  """Update an existing playbook."""
956
+ data: dict = {}
957
+ if title is not None:
958
+ data["title"] = title
959
+ if body is not None:
960
+ data["body"] = body
961
+ if macro is not None:
962
+ data["macro"] = macro
963
+ if not data:
964
+ console.print("[yellow]Nothing to update. Pass --title, --body, or --macro.[/yellow]")
965
+ return
898
966
  playbooks.update_playbook(playbook_id, title=title or "", body=body, macro=macro)
899
967
  console.print(f"[green]Playbook {playbook_id} updated.[/green]")
900
968
 
@@ -971,9 +1039,12 @@ def chain_cmd(
971
1039
  console.print(f"[bold cyan]Step {i+1}/{len(steps)}:[/bold cyan] Playbook={step_pb}")
972
1040
 
973
1041
  if i == 0:
974
- with console.status(f"Starting session..."):
1042
+ with console.status("Starting session..."):
975
1043
  resp = sessions.create_session(prompt=step_prompt, playbook_id=step_pb)
976
- current_sid = resp["session_id"]
1044
+ current_sid = resp.get("session_id")
1045
+ if not current_sid:
1046
+ console.print("[bold red]Error:[/bold red] No session ID returned from API.")
1047
+ raise typer.Exit(1)
977
1048
  config.current_session_id = current_sid
978
1049
  console.print(f"[green]Session started:[/green] {current_sid}")
979
1050
  else:
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