agentworks-cli 0.3.0__tar.gz → 0.4.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 (88) hide show
  1. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/CHANGELOG.md +28 -0
  2. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/PKG-INFO +25 -25
  3. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/README.md +24 -24
  4. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/agents/manager.py +132 -60
  5. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/catalog.py +3 -1
  6. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/cli.py +160 -92
  7. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/completions/bash.py +1 -1
  8. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/completions/powershell.py +1 -1
  9. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/completions/spec.py +8 -5
  10. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/completions/zsh.py +2 -2
  11. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/config.py +58 -10
  12. agentworks_cli-0.4.0/agentworks/errors.py +117 -0
  13. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/output.py +36 -53
  14. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/sample-config.toml +25 -2
  15. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/sessions/console.py +15 -7
  16. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/sessions/manager.py +159 -57
  17. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/sessions/multi_console.py +546 -73
  18. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/sources.py +3 -1
  19. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/ssh.py +7 -5
  20. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vm_hosts/manager.py +22 -5
  21. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/backup.py +14 -5
  22. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/initializer.py +12 -3
  23. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/manager.py +130 -35
  24. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/provisioners/azure.py +2 -1
  25. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/provisioners/lima.py +12 -5
  26. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/provisioners/proxmox_api.py +3 -1
  27. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/workspaces/backends/vm.py +9 -4
  28. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/workspaces/manager.py +199 -60
  29. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/pyproject.toml +1 -1
  30. agentworks_cli-0.4.0/tests/test_agents.py +67 -0
  31. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_completions.py +41 -0
  32. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_config.py +48 -0
  33. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_consoles.py +411 -18
  34. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/uv.lock +1 -1
  35. agentworks_cli-0.3.0/tests/test_agents.py +0 -30
  36. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/.gitignore +0 -0
  37. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/.python-version +0 -0
  38. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/__init__.py +0 -0
  39. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/agents/__init__.py +0 -0
  40. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/agents/templates.py +0 -0
  41. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/catalog.toml +0 -0
  42. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/completions/__init__.py +0 -0
  43. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/completions/install.py +0 -0
  44. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/db.py +0 -0
  45. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/doctor.py +0 -0
  46. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/git_credentials/__init__.py +0 -0
  47. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/git_credentials/azdo.py +0 -0
  48. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/git_credentials/base.py +0 -0
  49. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/git_credentials/github.py +0 -0
  50. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/nerf-config.yaml +0 -0
  51. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/remote_exec.py +0 -0
  52. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/sessions/__init__.py +0 -0
  53. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/sessions/templates.py +0 -0
  54. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/sessions/tmux.py +0 -0
  55. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/ssh_config.py +0 -0
  56. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vm_hosts/__init__.py +0 -0
  57. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/__init__.py +0 -0
  58. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/base.py +0 -0
  59. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/bootstrap_script.py +0 -0
  60. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/cloud_init.py +0 -0
  61. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/provisioners/__init__.py +0 -0
  62. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/provisioners/proxmox.py +0 -0
  63. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/provisioners/wsl2.py +0 -0
  64. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/vms/templates.py +0 -0
  65. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/workspaces/__init__.py +0 -0
  66. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/workspaces/backends/__init__.py +0 -0
  67. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/workspaces/templates.py +0 -0
  68. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/agentworks/workspaces/tmuxinator.py +0 -0
  69. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/pypi-dist/.gitignore +0 -0
  70. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/__init__.py +0 -0
  71. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/conftest.py +0 -0
  72. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_authorized_keys.py +0 -0
  73. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_bootstrap_script.py +0 -0
  74. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_catalog.py +0 -0
  75. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_cloud_init.py +0 -0
  76. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_db.py +0 -0
  77. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_doctor.py +0 -0
  78. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_error_wrapper.py +0 -0
  79. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_exec_target.py +0 -0
  80. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_initializer.py +0 -0
  81. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_name_validation.py +0 -0
  82. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_nerf_plugin.py +0 -0
  83. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_proxmox_api.py +0 -0
  84. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_remote_exec.py +0 -0
  85. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_session_liveness.py +0 -0
  86. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_ssh_config.py +0 -0
  87. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_templates.py +0 -0
  88. {agentworks_cli-0.3.0 → agentworks_cli-0.4.0}/tests/test_tmuxinator.py +0 -0
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.0](https://github.com/WayfarerLabs/agentworks/compare/v0.3.0...v0.4.0) (2026-06-01)
4
+
5
+
6
+ ### Features
7
+
8
+ * **console:** configurable layout and restore-session ([73bdf68](https://github.com/WayfarerLabs/agentworks/commit/73bdf681581825c8838398bc291839ddb8cbe82f))
9
+ * **console:** configurable layout and restore-session for accidental shell kills ([5d5ae48](https://github.com/WayfarerLabs/agentworks/commit/5d5ae487869ac31bb82bad21f1b9bc191e0aacae))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * **console:** include console name in untagged-pane warnings and check set-option result ([a48adf8](https://github.com/WayfarerLabs/agentworks/commit/a48adf80dfb78bb6264918b55fef1072d5898b10))
15
+ * **console:** only mirror swap-pane into local map on success ([7f33652](https://github.com/WayfarerLabs/agentworks/commit/7f336525aa27ef4eedefd0d93c0c9a517e8c1f72))
16
+ * **console:** pass console name through _live_best_effort and refine restore-session error messages ([eec0b62](https://github.com/WayfarerLabs/agentworks/commit/eec0b62afb14cd946ccc9234ef19266f5340a6bd))
17
+ * **console:** restore-session raises on any partial split/tag failure ([97f72aa](https://github.com/WayfarerLabs/agentworks/commit/97f72aa32c65923ffe43ff844219b3e92ef0976d))
18
+ * **console:** shlex-quote cd-failure diagnostic and reword restore-session help ([c0fa16b](https://github.com/WayfarerLabs/agentworks/commit/c0fa16bda5c0de0348a998b27d9e4e5c8b711fb5))
19
+ * **console:** validate restore-session tag permutation, allowlist named_console, clean docstring and sample-config ([72d3815](https://github.com/WayfarerLabs/agentworks/commit/72d3815ea7ac6a84b13e8be175f9bc6e99a47d0d))
20
+
21
+
22
+ ### Performance Improvements
23
+
24
+ * **console:** single-pass tag validation, in-memory reorder, warn on missing pane id ([c8f8362](https://github.com/WayfarerLabs/agentworks/commit/c8f8362cb6f07c45744650f5ee579fc8da605696))
25
+
26
+
27
+ ### Documentation
28
+
29
+ * **console:** clarify reinit_workspace docstring on detection vs always-applied steps ([b7e548b](https://github.com/WayfarerLabs/agentworks/commit/b7e548bcf3396f3fa339551c0b93485209a20890))
30
+
3
31
  ## [0.3.0](https://github.com/WayfarerLabs/agentworks/compare/v0.2.1...v0.3.0) (2026-06-01)
4
32
 
5
33
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentworks-cli
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: CLI for orchestrating workspace lifecycle across multiple compute targets
5
5
  Project-URL: Homepage, https://github.com/WayfarerLabs/agentworks
6
6
  Project-URL: Repository, https://github.com/WayfarerLabs/agentworks
@@ -380,7 +380,7 @@ Manage workspaces on VMs.
380
380
  | `agentworks workspace list` | List workspaces |
381
381
  | `agentworks workspace copy <source> <name>` | Copy a workspace to a new VM |
382
382
  | `agentworks workspace rehome <name>` | Move workspace to a new path |
383
- | `agentworks workspace repair <name>` | Repair workspace infrastructure |
383
+ | `agentworks workspace reinit <name>` | Reinit workspace infrastructure |
384
384
  | `agentworks workspace delete <name>` | Delete a workspace |
385
385
 
386
386
  `workspace create <name>` takes the workspace name as a required positional. Optional flags: `--vm`,
@@ -400,19 +400,18 @@ during deletion. Pass `--yes` to skip the confirmation prompt.
400
400
 
401
401
  Manage agents (isolated Linux users) on VMs. Agents are VM-scoped and access workspaces via grants.
402
402
 
403
- | Command | Description |
404
- | ------------------------------------------------------------ | ------------------------------ |
405
- | `agentworks agent create <name> [--vm]` | Create an agent on a VM |
406
- | `agentworks agent list [--vm <vm>]` | List agents |
407
- | `agentworks agent describe <name>` | Show agent details and grants |
408
- | `agentworks agent reinit <name>` | Re-run agent setup |
409
- | `agentworks agent workspace-grants grant <name> <ws>[,<ws>]` | Grant workspace access |
410
- | `agentworks agent workspace-grants grant <name> --all` | Grant access to all workspaces |
411
- | `agentworks agent workspace-grants deny <name> <ws>[,<ws>]` | Remove workspace access |
412
- | `agentworks agent workspace-grants deny <name> --all` | Remove all explicit grants |
413
- | `agentworks agent workspace-grants list <name>` | List workspace grants |
414
- | `agentworks agent shell <name> [--workspace <ws>]` | Shell into an agent |
415
- | `agentworks agent delete <name>` | Delete an agent |
403
+ | Command | Description |
404
+ | -------------------------------------------------- | ------------------------------ |
405
+ | `agentworks agent create <name> [--vm]` | Create an agent on a VM |
406
+ | `agentworks agent list [--vm <vm>]` | List agents |
407
+ | `agentworks agent describe <name>` | Show agent details and grants |
408
+ | `agentworks agent reinit <name>` | Re-run agent setup |
409
+ | `agentworks agent grant-workspace <name> <ws>...` | Grant workspace access |
410
+ | `agentworks agent grant-workspace <name> --all` | Grant access to all workspaces |
411
+ | `agentworks agent revoke-workspace <name> <ws>...` | Revoke workspace access |
412
+ | `agentworks agent revoke-workspace <name> --all` | Revoke all explicit grants |
413
+ | `agentworks agent shell <name> [--workspace <ws>]` | Shell into an agent |
414
+ | `agentworks agent delete <name>` | Delete an agent |
416
415
 
417
416
  `agent create <name>` takes the agent name as a required positional. Optional flags: `--vm`,
418
417
  `--template`, and `--grant-all-workspaces`.
@@ -564,8 +563,9 @@ aw-console-backend
564
563
 
565
564
  The tmux session is built lazily on first `attach` (or rebuilt with `--recreate`). Adding or
566
565
  removing sessions/shells while the console is attached updates tmux immediately; when offline, only
567
- the DB is touched and changes appear on next attach. The console does not auto-boot the VM for live
568
- sync; VM start happens only on explicit `attach`.
566
+ the DB is touched and changes appear on next attach. The mutation commands (`add-session`,
567
+ `remove-session`, `add-shell`) never auto-boot the VM; the explicit attach/repair commands
568
+ (`attach`, `restore-session`) do start a stopped VM, since their job is to bring live state up.
569
569
 
570
570
  #### VM console (deprecated)
571
571
 
@@ -610,17 +610,17 @@ Template commands support `{{session_name}}` and `{{workspace_name}}` variable s
610
610
  by `session restart` -- useful for tools like Claude Code where `--resume` picks up the previous
611
611
  conversation. If omitted, the regular `command` is used.
612
612
 
613
- ### Installers
613
+ ### Catalog
614
614
 
615
615
  Browse and inspect the built-in catalog of installable tools.
616
616
 
617
- | Command | Description |
618
- | -------------------------------------- | ---------------------------------- |
619
- | `agentworks installer list` | List all available catalog entries |
620
- | `agentworks installer describe <name>` | Show details of a catalog entry |
617
+ | Command | Description |
618
+ | ------------------------------------ | ---------------------------------- |
619
+ | `agentworks catalog list` | List all available catalog entries |
620
+ | `agentworks catalog describe <name>` | Show details of a catalog entry |
621
621
 
622
- `installer list` accepts `--type` (apt-source, apt-package, system-install-cmd, user-install-cmd)
623
- and `--source` (builtin, user) filters.
622
+ `catalog list` accepts `--type` (apt-source, apt-package, system-install-cmd, user-install-cmd) and
623
+ `--source` (built-in, custom) filters.
624
624
 
625
625
  ### Config
626
626
 
@@ -696,7 +696,7 @@ standalone via `claude plugin marketplace add`.
696
696
  ### Built-in Catalog
697
697
 
698
698
  Agentworks ships a built-in catalog of common tools (apt sources, apt packages, system install
699
- commands, and user install commands). Run `agentworks installer list` to see what is available.
699
+ commands, and user install commands). Run `agentworks catalog list` to see what is available.
700
700
  Reference catalog entries by name in `vm_templates`, `admin.config`, and `agent_templates`.
701
701
  User-defined entries in your config override built-in entries with the same name.
702
702
 
@@ -363,7 +363,7 @@ Manage workspaces on VMs.
363
363
  | `agentworks workspace list` | List workspaces |
364
364
  | `agentworks workspace copy <source> <name>` | Copy a workspace to a new VM |
365
365
  | `agentworks workspace rehome <name>` | Move workspace to a new path |
366
- | `agentworks workspace repair <name>` | Repair workspace infrastructure |
366
+ | `agentworks workspace reinit <name>` | Reinit workspace infrastructure |
367
367
  | `agentworks workspace delete <name>` | Delete a workspace |
368
368
 
369
369
  `workspace create <name>` takes the workspace name as a required positional. Optional flags: `--vm`,
@@ -383,19 +383,18 @@ during deletion. Pass `--yes` to skip the confirmation prompt.
383
383
 
384
384
  Manage agents (isolated Linux users) on VMs. Agents are VM-scoped and access workspaces via grants.
385
385
 
386
- | Command | Description |
387
- | ------------------------------------------------------------ | ------------------------------ |
388
- | `agentworks agent create <name> [--vm]` | Create an agent on a VM |
389
- | `agentworks agent list [--vm <vm>]` | List agents |
390
- | `agentworks agent describe <name>` | Show agent details and grants |
391
- | `agentworks agent reinit <name>` | Re-run agent setup |
392
- | `agentworks agent workspace-grants grant <name> <ws>[,<ws>]` | Grant workspace access |
393
- | `agentworks agent workspace-grants grant <name> --all` | Grant access to all workspaces |
394
- | `agentworks agent workspace-grants deny <name> <ws>[,<ws>]` | Remove workspace access |
395
- | `agentworks agent workspace-grants deny <name> --all` | Remove all explicit grants |
396
- | `agentworks agent workspace-grants list <name>` | List workspace grants |
397
- | `agentworks agent shell <name> [--workspace <ws>]` | Shell into an agent |
398
- | `agentworks agent delete <name>` | Delete an agent |
386
+ | Command | Description |
387
+ | -------------------------------------------------- | ------------------------------ |
388
+ | `agentworks agent create <name> [--vm]` | Create an agent on a VM |
389
+ | `agentworks agent list [--vm <vm>]` | List agents |
390
+ | `agentworks agent describe <name>` | Show agent details and grants |
391
+ | `agentworks agent reinit <name>` | Re-run agent setup |
392
+ | `agentworks agent grant-workspace <name> <ws>...` | Grant workspace access |
393
+ | `agentworks agent grant-workspace <name> --all` | Grant access to all workspaces |
394
+ | `agentworks agent revoke-workspace <name> <ws>...` | Revoke workspace access |
395
+ | `agentworks agent revoke-workspace <name> --all` | Revoke all explicit grants |
396
+ | `agentworks agent shell <name> [--workspace <ws>]` | Shell into an agent |
397
+ | `agentworks agent delete <name>` | Delete an agent |
399
398
 
400
399
  `agent create <name>` takes the agent name as a required positional. Optional flags: `--vm`,
401
400
  `--template`, and `--grant-all-workspaces`.
@@ -547,8 +546,9 @@ aw-console-backend
547
546
 
548
547
  The tmux session is built lazily on first `attach` (or rebuilt with `--recreate`). Adding or
549
548
  removing sessions/shells while the console is attached updates tmux immediately; when offline, only
550
- the DB is touched and changes appear on next attach. The console does not auto-boot the VM for live
551
- sync; VM start happens only on explicit `attach`.
549
+ the DB is touched and changes appear on next attach. The mutation commands (`add-session`,
550
+ `remove-session`, `add-shell`) never auto-boot the VM; the explicit attach/repair commands
551
+ (`attach`, `restore-session`) do start a stopped VM, since their job is to bring live state up.
552
552
 
553
553
  #### VM console (deprecated)
554
554
 
@@ -593,17 +593,17 @@ Template commands support `{{session_name}}` and `{{workspace_name}}` variable s
593
593
  by `session restart` -- useful for tools like Claude Code where `--resume` picks up the previous
594
594
  conversation. If omitted, the regular `command` is used.
595
595
 
596
- ### Installers
596
+ ### Catalog
597
597
 
598
598
  Browse and inspect the built-in catalog of installable tools.
599
599
 
600
- | Command | Description |
601
- | -------------------------------------- | ---------------------------------- |
602
- | `agentworks installer list` | List all available catalog entries |
603
- | `agentworks installer describe <name>` | Show details of a catalog entry |
600
+ | Command | Description |
601
+ | ------------------------------------ | ---------------------------------- |
602
+ | `agentworks catalog list` | List all available catalog entries |
603
+ | `agentworks catalog describe <name>` | Show details of a catalog entry |
604
604
 
605
- `installer list` accepts `--type` (apt-source, apt-package, system-install-cmd, user-install-cmd)
606
- and `--source` (builtin, user) filters.
605
+ `catalog list` accepts `--type` (apt-source, apt-package, system-install-cmd, user-install-cmd) and
606
+ `--source` (built-in, custom) filters.
607
607
 
608
608
  ### Config
609
609
 
@@ -679,7 +679,7 @@ standalone via `claude plugin marketplace add`.
679
679
  ### Built-in Catalog
680
680
 
681
681
  Agentworks ships a built-in catalog of common tools (apt sources, apt packages, system install
682
- commands, and user install commands). Run `agentworks installer list` to see what is available.
682
+ commands, and user install commands). Run `agentworks catalog list` to see what is available.
683
683
  Reference catalog entries by name in `vm_templates`, `admin.config`, and `agent_templates`.
684
684
  User-defined entries in your config override built-in entries with the same name.
685
685
 
@@ -6,7 +6,15 @@ from typing import TYPE_CHECKING
6
6
 
7
7
  from agentworks import output
8
8
  from agentworks.config import validate_name
9
- from agentworks.output import AgentError, VMError
9
+ from agentworks.errors import (
10
+ AlreadyExistsError,
11
+ AuthorizationError,
12
+ ExternalError,
13
+ NotFoundError,
14
+ StateError,
15
+ UserAbort,
16
+ ValidationError,
17
+ )
10
18
  from agentworks.ssh import admin_exec_target
11
19
 
12
20
  if TYPE_CHECKING:
@@ -110,7 +118,11 @@ def create_agent(
110
118
  validate_name(name)
111
119
 
112
120
  if db.get_agent(name) is not None:
113
- raise AgentError(f"agent '{name}' already exists")
121
+ raise AlreadyExistsError(
122
+ f"agent '{name}' already exists",
123
+ entity_kind="agent",
124
+ entity_name=name,
125
+ )
114
126
 
115
127
  vm = _require_vm(db, vm_name)
116
128
  linux_user = derive_linux_user(name)
@@ -146,7 +158,12 @@ def create_agent(
146
158
  raise
147
159
  except Exception as e:
148
160
  _safe_rollback()
149
- raise AgentError(f"creating agent: {e}\nSSH log: {ssh_logger.path}") from None
161
+ raise ExternalError(
162
+ f"creating agent: {e}",
163
+ entity_kind="agent",
164
+ entity_name=name,
165
+ hint=f"SSH log: {ssh_logger.path}",
166
+ ) from e
150
167
  finally:
151
168
  ssh_logger.close()
152
169
 
@@ -178,7 +195,11 @@ def delete_agent(
178
195
  """Delete an agent from a VM."""
179
196
  agent = db.get_agent(name)
180
197
  if agent is None:
181
- raise AgentError(f"agent '{name}' not found")
198
+ raise NotFoundError(
199
+ f"agent '{name}' not found",
200
+ entity_kind="agent",
201
+ entity_name=name,
202
+ )
182
203
 
183
204
  # Check for sessions using this agent
184
205
  all_sessions = db.list_sessions()
@@ -186,8 +207,11 @@ def delete_agent(
186
207
  if agent_sessions and not force:
187
208
  for s in agent_sessions:
188
209
  output.detail(f"{s.name}")
189
- raise AgentError(
190
- f"agent '{name}' has {len(agent_sessions)} session(s). Delete them first, or use --force."
210
+ raise StateError(
211
+ f"agent '{name}' has {len(agent_sessions)} session(s).",
212
+ entity_kind="agent",
213
+ entity_name=name,
214
+ hint="Delete the sessions first, or pass --force to also stop them.",
191
215
  )
192
216
 
193
217
  if not yes:
@@ -195,7 +219,7 @@ def delete_agent(
195
219
  if agent_sessions:
196
220
  msg += f" ({len(agent_sessions)} session(s) will also be stopped)"
197
221
  if not output.confirm(msg):
198
- raise output.UserAbort("delete cancelled")
222
+ raise UserAbort("delete cancelled")
199
223
 
200
224
  vm = _require_vm(db, agent.vm_name)
201
225
 
@@ -232,9 +256,12 @@ def delete_agent(
232
256
  elif status == SessionStatus.UNKNOWN:
233
257
  unstoppable.append(session.name)
234
258
  if unstoppable:
235
- raise AgentError(
259
+ raise StateError(
236
260
  f"cannot delete agent '{name}': {len(unstoppable)} session(s) could not be stopped "
237
- f"({', '.join(unstoppable)}). Resolve manually before retrying."
261
+ f"({', '.join(unstoppable)}).",
262
+ entity_kind="agent",
263
+ entity_name=name,
264
+ hint="Resolve the stuck sessions manually before retrying.",
238
265
  )
239
266
  for session in agent_sessions:
240
267
  db.delete_session(session.name)
@@ -266,7 +293,11 @@ def reinit_agent(
266
293
 
267
294
  agent = db.get_agent(name)
268
295
  if agent is None:
269
- raise AgentError(f"agent '{name}' not found")
296
+ raise NotFoundError(
297
+ f"agent '{name}' not found",
298
+ entity_kind="agent",
299
+ entity_name=name,
300
+ )
270
301
 
271
302
  agent_tmpl = resolve_template(config, agent.template)
272
303
  if agent.template and agent.template != "default":
@@ -290,7 +321,12 @@ def reinit_agent(
290
321
  )
291
322
  raise
292
323
  except Exception as e:
293
- raise AgentError(f"reinitializing agent: {e}\nSSH log: {ssh_logger.path}") from None
324
+ raise ExternalError(
325
+ f"reinitializing agent: {e}",
326
+ entity_kind="agent",
327
+ entity_name=name,
328
+ hint=f"SSH log: {ssh_logger.path}",
329
+ ) from e
294
330
  finally:
295
331
  ssh_logger.close()
296
332
 
@@ -370,7 +406,11 @@ def describe_agent(
370
406
  """Show detailed information about an agent."""
371
407
  agent = db.get_agent(name)
372
408
  if agent is None:
373
- raise AgentError(f"agent '{name}' not found")
409
+ raise NotFoundError(
410
+ f"agent '{name}' not found",
411
+ entity_kind="agent",
412
+ entity_name=name,
413
+ )
374
414
 
375
415
  output.info(f"Name: {agent.name}")
376
416
  output.info(f"VM: {agent.vm_name}")
@@ -410,7 +450,11 @@ def shell_agent(
410
450
  """Open a shell as an agent user on a VM."""
411
451
  agent = db.get_agent(name)
412
452
  if agent is None:
413
- raise AgentError(f"agent '{name}' not found")
453
+ raise NotFoundError(
454
+ f"agent '{name}' not found",
455
+ entity_kind="agent",
456
+ entity_name=name,
457
+ )
414
458
 
415
459
  vm = _require_vm(db, agent.vm_name)
416
460
 
@@ -427,9 +471,18 @@ def shell_agent(
427
471
  if workspace_name:
428
472
  ws = db.get_workspace(workspace_name)
429
473
  if ws is None:
430
- raise AgentError(f"workspace '{workspace_name}' not found")
474
+ raise NotFoundError(
475
+ f"workspace '{workspace_name}' not found",
476
+ entity_kind="workspace",
477
+ entity_name=workspace_name,
478
+ )
431
479
  if not db.has_any_grant(name, workspace_name):
432
- raise AgentError(f"agent '{name}' does not have access to workspace '{workspace_name}'")
480
+ raise AuthorizationError(
481
+ f"agent '{name}' does not have access to workspace '{workspace_name}'",
482
+ entity_kind="agent",
483
+ entity_name=name,
484
+ hint=f"Run 'agent grant-workspace {name} {workspace_name}' to grant access.",
485
+ )
433
486
  import shlex
434
487
 
435
488
  q_path = shlex.quote(ws.workspace_path)
@@ -454,11 +507,20 @@ def exec_agent(
454
507
 
455
508
  agent = db.get_agent(name)
456
509
  if agent is None:
457
- raise AgentError(f"agent '{name}' not found")
510
+ raise NotFoundError(
511
+ f"agent '{name}' not found",
512
+ entity_kind="agent",
513
+ entity_name=name,
514
+ )
458
515
 
459
516
  vm = _require_vm(db, agent.vm_name)
460
517
  if vm.tailscale_host is None:
461
- raise AgentError(f"VM '{vm.name}' has no Tailscale IP (init may not be complete)")
518
+ raise StateError(
519
+ f"VM '{vm.name}' has no Tailscale IP",
520
+ entity_kind="vm",
521
+ entity_name=vm.name,
522
+ hint="VM init may not be complete. Check 'vm describe' for status.",
523
+ )
462
524
 
463
525
  remote_cmd = command[0] if len(command) == 1 else shlex.join(command)
464
526
  su_cmd = f"sudo -n su --login {agent.linux_user} -c {shlex.quote(remote_cmd)}"
@@ -484,9 +546,21 @@ def grant_workspaces(
484
546
  grant_all: bool = False,
485
547
  ) -> None:
486
548
  """Grant an agent explicit access to workspaces."""
549
+ if not grant_all and not workspace_names:
550
+ raise ValidationError(
551
+ f"grant for '{agent_name}' needs at least one workspace name "
552
+ f"or workspace_names empty + grant_all=True",
553
+ entity_kind="agent",
554
+ entity_name=agent_name,
555
+ )
556
+
487
557
  agent = db.get_agent(agent_name)
488
558
  if agent is None:
489
- raise AgentError(f"agent '{agent_name}' not found")
559
+ raise NotFoundError(
560
+ f"agent '{agent_name}' not found",
561
+ entity_kind="agent",
562
+ entity_name=agent_name,
563
+ )
490
564
 
491
565
  vm = _require_vm(db, agent.vm_name)
492
566
 
@@ -509,22 +583,34 @@ def grant_workspaces(
509
583
  output.detail(f"Granted: {ws_name}")
510
584
 
511
585
 
512
- def deny_workspaces(
586
+ def revoke_workspaces(
513
587
  db: Database,
514
588
  config: Config,
515
589
  *,
516
590
  agent_name: str,
517
591
  workspace_names: list[str],
518
- deny_all: bool = False,
592
+ revoke_all: bool = False,
519
593
  ) -> None:
520
- """Remove explicit workspace grants from an agent."""
594
+ """Revoke explicit workspace grants from an agent."""
595
+ if not revoke_all and not workspace_names:
596
+ raise ValidationError(
597
+ f"revoke for '{agent_name}' needs at least one workspace name "
598
+ f"or workspace_names empty + revoke_all=True",
599
+ entity_kind="agent",
600
+ entity_name=agent_name,
601
+ )
602
+
521
603
  agent = db.get_agent(agent_name)
522
604
  if agent is None:
523
- raise AgentError(f"agent '{agent_name}' not found")
605
+ raise NotFoundError(
606
+ f"agent '{agent_name}' not found",
607
+ entity_kind="agent",
608
+ entity_name=agent_name,
609
+ )
524
610
 
525
611
  vm = _require_vm(db, agent.vm_name)
526
612
 
527
- if deny_all:
613
+ if revoke_all:
528
614
  db.update_agent_grant_all(agent_name, False)
529
615
  db.delete_explicit_grants(agent_name)
530
616
  # Remove from groups where no implicit grants remain
@@ -535,7 +621,7 @@ def deny_workspaces(
535
621
  remaining_implicit.append(ws_name)
536
622
  else:
537
623
  _remove_from_workspace_group(vm, config, db, agent.linux_user, ws_name, logger=None)
538
- output.info(f"All explicit grants removed for agent '{agent_name}'")
624
+ output.info(f"All explicit grants revoked for agent '{agent_name}'")
539
625
  if remaining_implicit:
540
626
  output.warn(
541
627
  f"agent still has implicit access via sessions to: {', '.join(remaining_implicit)}"
@@ -546,39 +632,9 @@ def deny_workspaces(
546
632
  db.delete_agent_grant(agent_name, ws_name, "explicit")
547
633
  if not db.has_any_grant(agent_name, ws_name):
548
634
  _remove_from_workspace_group(vm, config, db, agent.linux_user, ws_name, logger=None)
549
- output.detail(f"Denied: {ws_name}")
635
+ output.detail(f"Revoked: {ws_name}")
550
636
  else:
551
- output.detail(f"Denied: {ws_name} (still has implicit access via sessions)")
552
-
553
-
554
- def list_grants(
555
- db: Database,
556
- *,
557
- agent_name: str,
558
- ) -> None:
559
- """List workspace grants for an agent."""
560
- agent = db.get_agent(agent_name)
561
- if agent is None:
562
- raise AgentError(f"agent '{agent_name}' not found")
563
-
564
- if agent.grant_all:
565
- output.info(f"Agent '{agent_name}' has grant-all enabled (access to all workspaces)")
566
-
567
- grants = db.list_granted_workspaces_with_types(agent_name)
568
- if not grants:
569
- output.info("No workspace grants.")
570
- return
571
-
572
- output.info(f"{'WORKSPACE':<25} {'TYPE'}")
573
- output.info("-" * 45)
574
- for ws_name, has_explicit, has_implicit in grants:
575
- if has_explicit and has_implicit:
576
- grant_type = "explicit + implicit"
577
- elif has_explicit:
578
- grant_type = "explicit"
579
- else:
580
- grant_type = "implicit (via sessions)"
581
- output.info(f"{ws_name:<25} {grant_type}")
637
+ output.detail(f"Revoked: {ws_name} (still has implicit access via sessions)")
582
638
 
583
639
 
584
640
  # -- VM operations ---------------------------------------------------------
@@ -593,7 +649,11 @@ def _resolve_ws_group(db: Database, workspace_name: str) -> str:
593
649
  """
594
650
  ws = db.get_workspace(workspace_name)
595
651
  if ws is None:
596
- raise AgentError(f"workspace '{workspace_name}' not found")
652
+ raise NotFoundError(
653
+ f"workspace '{workspace_name}' not found",
654
+ entity_kind="workspace",
655
+ entity_name=workspace_name,
656
+ )
597
657
  return ws.linux_group
598
658
 
599
659
 
@@ -1127,19 +1187,31 @@ def _build_agent_test_command(
1127
1187
  def _require_vm(db: Database, vm_name: str) -> VMRow:
1128
1188
  vm = db.get_vm(vm_name)
1129
1189
  if vm is None:
1130
- raise VMError(f"VM '{vm_name}' not found")
1190
+ raise NotFoundError(
1191
+ f"VM '{vm_name}' not found",
1192
+ entity_kind="vm",
1193
+ entity_name=vm_name,
1194
+ )
1131
1195
  return vm
1132
1196
 
1133
1197
 
1134
1198
  def _require_workspace(db: Database, workspace_name: str) -> WorkspaceRow:
1135
1199
  ws = db.get_workspace(workspace_name)
1136
1200
  if ws is None:
1137
- raise AgentError(f"workspace '{workspace_name}' not found")
1201
+ raise NotFoundError(
1202
+ f"workspace '{workspace_name}' not found",
1203
+ entity_kind="workspace",
1204
+ entity_name=workspace_name,
1205
+ )
1138
1206
  return ws
1139
1207
 
1140
1208
 
1141
1209
  def _require_vm_for_workspace(db: Database, ws: WorkspaceRow) -> VMRow:
1142
1210
  vm = db.get_vm(ws.vm_name)
1143
1211
  if vm is None:
1144
- raise VMError(f"VM '{ws.vm_name}' not found")
1212
+ raise NotFoundError(
1213
+ f"VM '{ws.vm_name}' not found",
1214
+ entity_kind="vm",
1215
+ entity_name=ws.vm_name,
1216
+ )
1145
1217
  return vm
@@ -13,11 +13,13 @@ from dataclasses import dataclass, field
13
13
  from pathlib import Path
14
14
  from typing import TYPE_CHECKING
15
15
 
16
+ from agentworks.errors import ExternalError
17
+
16
18
  if TYPE_CHECKING:
17
19
  from agentworks.config import Config
18
20
 
19
21
 
20
- class CatalogError(Exception):
22
+ class CatalogError(ExternalError):
21
23
  """Raised when the catalog is invalid."""
22
24
 
23
25