agentworks-cli 0.2.1__py3-none-any.whl

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 (59) hide show
  1. agentworks/__init__.py +1 -0
  2. agentworks/agents/__init__.py +0 -0
  3. agentworks/agents/manager.py +1095 -0
  4. agentworks/agents/templates.py +145 -0
  5. agentworks/catalog.py +264 -0
  6. agentworks/catalog.toml +131 -0
  7. agentworks/cli.py +1462 -0
  8. agentworks/completions/__init__.py +33 -0
  9. agentworks/completions/bash.py +179 -0
  10. agentworks/completions/install.py +122 -0
  11. agentworks/completions/powershell.py +270 -0
  12. agentworks/completions/spec.py +216 -0
  13. agentworks/completions/zsh.py +256 -0
  14. agentworks/config.py +894 -0
  15. agentworks/db.py +1083 -0
  16. agentworks/doctor.py +430 -0
  17. agentworks/git_credentials/__init__.py +0 -0
  18. agentworks/git_credentials/azdo.py +29 -0
  19. agentworks/git_credentials/base.py +71 -0
  20. agentworks/git_credentials/github.py +22 -0
  21. agentworks/nerf-config.yaml +16 -0
  22. agentworks/output.py +296 -0
  23. agentworks/remote_exec.py +286 -0
  24. agentworks/sample-config.toml +289 -0
  25. agentworks/sessions/__init__.py +0 -0
  26. agentworks/sessions/console.py +164 -0
  27. agentworks/sessions/manager.py +1297 -0
  28. agentworks/sessions/templates.py +101 -0
  29. agentworks/sessions/tmux.py +503 -0
  30. agentworks/sources.py +303 -0
  31. agentworks/ssh.py +759 -0
  32. agentworks/ssh_config.py +255 -0
  33. agentworks/vm_hosts/__init__.py +0 -0
  34. agentworks/vm_hosts/manager.py +86 -0
  35. agentworks/vms/__init__.py +0 -0
  36. agentworks/vms/backup.py +409 -0
  37. agentworks/vms/base.py +56 -0
  38. agentworks/vms/bootstrap_script.py +185 -0
  39. agentworks/vms/cloud_init.py +55 -0
  40. agentworks/vms/initializer.py +1523 -0
  41. agentworks/vms/manager.py +1122 -0
  42. agentworks/vms/provisioners/__init__.py +0 -0
  43. agentworks/vms/provisioners/azure.py +602 -0
  44. agentworks/vms/provisioners/lima.py +295 -0
  45. agentworks/vms/provisioners/proxmox.py +279 -0
  46. agentworks/vms/provisioners/proxmox_api.py +261 -0
  47. agentworks/vms/provisioners/wsl2.py +340 -0
  48. agentworks/vms/templates.py +152 -0
  49. agentworks/workspaces/__init__.py +0 -0
  50. agentworks/workspaces/backends/__init__.py +0 -0
  51. agentworks/workspaces/backends/local.py +119 -0
  52. agentworks/workspaces/backends/vm.py +175 -0
  53. agentworks/workspaces/manager.py +1080 -0
  54. agentworks/workspaces/templates.py +76 -0
  55. agentworks/workspaces/tmuxinator.py +80 -0
  56. agentworks_cli-0.2.1.dist-info/METADATA +635 -0
  57. agentworks_cli-0.2.1.dist-info/RECORD +59 -0
  58. agentworks_cli-0.2.1.dist-info/WHEEL +4 -0
  59. agentworks_cli-0.2.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,289 @@
1
+ # Agentworks configuration
2
+ # Copy to ~/.config/agentworks/config.toml and edit to match your setup.
3
+ #
4
+ # Required fields are uncommented. Everything else shows the default value
5
+ # commented out -- uncomment and change only what you need.
6
+ #
7
+ # Agentworks ships a built-in catalog of common tools (apt sources, apt
8
+ # packages, system install commands, user install commands). Run
9
+ # `agentworks installer list` to see what is available. You can reference
10
+ # catalog entries by name in templates and define your own entries below
11
+ # to extend or override the catalog.
12
+
13
+ # =============================================================================
14
+ # Operator identity and platform settings
15
+ # =============================================================================
16
+
17
+ # --- Operator identity ---
18
+ # These settings identify you (the operator) to agentworks and your VMs.
19
+
20
+ [operator]
21
+ ssh_public_key = "~/.ssh/id_ed25519.pub" # required: used to authorize access to VMs
22
+ ssh_private_key = "~/.ssh/id_ed25519" # required: used for SSH connections to VMs
23
+ # ssh_config = "~/.ssh/config" # SSH config file for VM host entries
24
+ # ssh_config_dir = true # manage VM entries in ~/.ssh/config.d/
25
+ # ssh_host_prefix = "awvm--" # prefix for SSH host aliases (e.g. awvm--myvm)
26
+ # extra_ssh_public_keys = [ # additional public keys for VM authorized_keys
27
+ # "~/.ssh/other_key.pub",
28
+ # ]
29
+
30
+ # --- Paths ---
31
+
32
+ [paths]
33
+ # local_workspaces = "~/workspaces" # local workspace directory
34
+ # vm_workspaces = "/opt/agentworks/workspaces" # workspace directory on VMs
35
+ # vscode_workspaces = "~/aw-vscode-workspaces" # .code-workspace file directory
36
+ # backups = "~/.config/agentworks/backups" # vm backup directory
37
+
38
+ # --- Default values for CLI flags ---
39
+
40
+ [defaults]
41
+ # platform = "lima" # lima, azure, or wsl2
42
+ # vm_host = "mac-studio" # default VM host for remote Lima
43
+
44
+ # --- Git credential providers ---
45
+ # Define how to obtain git tokens. Referenced by name in admin.config and
46
+ # agent_templates. Tokens are prompted during VM init (or read from env vars)
47
+ # and stored via git credential-store on the VM.
48
+
49
+ # [git_credentials.github]
50
+ # type = "github"
51
+ # description = "GitHub full access" # optional, shown in prompts and logs
52
+ # Token: create at https://github.com/settings/tokens (repo scope)
53
+
54
+ # [git_credentials.azdo]
55
+ # type = "azdo"
56
+ # org = "my-org"
57
+ # description = "Azure DevOps for my-org" # optional, shown in prompts and logs
58
+ # Token: create at https://dev.azure.com/{org}/_usersSettings/tokens
59
+
60
+ # --- Azure settings (only needed for --platform azure) ---
61
+
62
+ # [azure]
63
+ # subscription_id = "..." # required for azure platform
64
+ # resource_group = "agentworks-vms" # required for azure platform
65
+ # region = "eastus2" # required for azure platform
66
+
67
+ # --- Proxmox settings (only needed for --platform proxmox) ---
68
+
69
+ # [proxmox]
70
+ # api_url = "https://pve.example.com:8006" # required: Proxmox API URL
71
+ # node = "pve" # required: Proxmox node name
72
+ # token_id = "agentworks@pam!agentworks" # required: API token ID
73
+ # template_vmid = 9000 # required: VMID of Debian 12 cloud-init template
74
+ # storage = "local-lvm" # target storage for cloned disks
75
+ # bridge = "vmbr0" # network bridge
76
+ # pool = "agentworks" # Proxmox resource pool for VMs
77
+ # verify_ssl = true # verify TLS certificate
78
+ # Token secret: set PROXMOX_TOKEN_SECRET environment variable
79
+
80
+ # =============================================================================
81
+ # Templates
82
+ # =============================================================================
83
+ #
84
+ # Agentworks uses templates for VMs, workspaces, agents, and tasks. Each
85
+ # template type supports a "default" entry used when --template is not
86
+ # specified. Templates support inheritance via the "inherits" key:
87
+ # child values override parents, depth-first left-to-right.
88
+
89
+ # --- VM templates ---
90
+ # System-level VM configuration: hardware resources, system packages, and
91
+ # system-wide tools. Selected at vm create time with --template.
92
+ #
93
+ # Agentworks always installs system packages (git, tmux, tmuxinator, acl, jq,
94
+ # unzip) during init, in addition to provisioning packages (openssh-server,
95
+ # curl, sudo, ca-certificates, gnupg) installed at VM create time.
96
+
97
+ [vm_templates.default]
98
+ # -- Provisioning (set once at create time) --
99
+ # cpus = 4 # number of CPUs (Lima)
100
+ # memory = 8 # memory in GiB (Lima)
101
+ # azure_vm_size = "Standard_B2s" # Azure VM size (determines cpu and memory for Azure)
102
+ # disk = 50 # disk size in GiB
103
+ # swap = 4 # swap file size in GiB (0 to disable)
104
+ # -- System-wide initialization (applied on create and reinit) --
105
+ # apt = [ # direct apt packages
106
+ # "zsh",
107
+ # "unzip",
108
+ # "build-essential",
109
+ # "ripgrep",
110
+ # "fzf",
111
+ # "fd-find",
112
+ # ]
113
+ # apt_packages = ["gh", "docker"] # named apt package sets from catalog
114
+ # snap = [] # direct snap packages
115
+ # system_install_commands = ["az-cli"] # system-level commands from catalog
116
+ # -- Nerf tools --
117
+ # nerf_build_claude_plugin = false # build Claude Code nerf tools plugin
118
+ # skip_nerf_defaults = false # skip built-in nerf catalog
119
+ # nerf_addl_manifests = [ # additional manifest files to merge in
120
+ # "~/.config/agentworks/nerf/my-tools/manifest.yaml",
121
+ # ]
122
+ # nerf_home_dir = "/opt/agentworks/nerf" # root dir for nerf artifacts
123
+
124
+ # [vm_templates.heavy]
125
+ # inherits = ["default"]
126
+ # cpus = 16
127
+ # memory = 64
128
+ # disk = 200
129
+
130
+ # --- Admin user configuration ---
131
+ # Per-user settings for the admin (operator) user on VMs. Applied during
132
+ # init/reinit. This is not a template; there is one admin config.
133
+
134
+ # [admin.config]
135
+ # username = "agentworks" # admin username on VMs (default: agentworks)
136
+ # shell = "zsh" # admin shell (default: zsh)
137
+ # git_credentials = ["github"] # git credential providers to configure
138
+ # user_install_commands = ["bun"] # per-user commands from catalog
139
+ # -- Dotfiles (source refs: local path or git::, see docs/guides/source-refs.md) --
140
+ # dotfiles_source = "git::https://github.com/user/dotfiles"
141
+ # dotfiles_destination = "~/.dotfiles" # where to put them on the VM
142
+ # dotfiles_install_cmd = "./install.sh" # command to run after clone/copy
143
+ # -- Mise (see docs/guides/mise.md) --
144
+ # mise_activate = true # add mise activate to shell profile
145
+ # mise_packages = ["terraform@1.14.5", "adr-tools@3.0.0"] # name@version tool declarations
146
+ # mise_lockfile = "~/.config/agentworks/mise.lock" # source ref (wins over dotfiles)
147
+ # mise_allow_unlocked = false # install packages not in lockfile (with warning)
148
+ # mise_install_before = "7d" # reject versions newer than this
149
+ # mise_prune_on_reinit = true # remove stale tool versions on reinit
150
+ # -- Git --
151
+ # git_force_safe_directory = true # set safe.directory '*' for admin and agents
152
+ # -- Nerf tools --
153
+ # nerf_install_claude_plugin = false # install nerf plugin for this user
154
+ # -- Claude Code (requires claude CLI on PATH, e.g. via user_install_commands) --
155
+ # claude_marketplaces = ["https://github.com/WayfarerLabs/nerftools#1.2.0"]
156
+ # claude_plugins = ["nerftools-default@nerftools"]
157
+
158
+ # --- Agent templates ---
159
+ # Per-user settings for agent users. Selected at agent create time with
160
+ # --template. Agents default to nothing unless explicitly configured.
161
+
162
+ # [agent_templates.default]
163
+ # shell = "bash" # agent shell (default: bash)
164
+ # git_credentials = [] # git credential providers (default: none)
165
+ # user_install_commands = [] # per-user commands from catalog
166
+ # -- Dotfiles --
167
+ # dotfiles_source = "git::https://github.com/user/agent-dotfiles"
168
+ # dotfiles_destination = "~/.dotfiles"
169
+ # dotfiles_install_cmd = "./install.sh"
170
+ # -- Mise --
171
+ # mise_activate = true # add mise activate to agent shell profile
172
+ # mise_packages = ["terraform@1.14.5"] # name@version tool declarations
173
+ # mise_lockfile = "~/.config/agentworks/agent-mise.lock"
174
+ # mise_allow_unlocked = false
175
+ # mise_install_before = "7d"
176
+ # mise_prune_on_reinit = true
177
+ # -- Nerf tools --
178
+ # nerf_install_claude_plugin = false # install nerf plugin for this agent
179
+ # -- Claude Code (requires claude CLI on PATH, e.g. via user_install_commands) --
180
+ # claude_marketplaces = ["https://github.com/WayfarerLabs/nerftools#1.2.0"]
181
+ # claude_plugins = ["nerftools-default@nerftools"]
182
+
183
+ # [agent_templates.restricted]
184
+ # inherits = ["default"]
185
+ # user_install_commands = []
186
+ # mise_packages = []
187
+
188
+ # --- Workspace templates ---
189
+ # Selected at workspace create time with --template. The "default" template
190
+ # is used when --template is not specified.
191
+ #
192
+ # Repo URLs: use HTTPS format. Git credentials configured in admin.config
193
+ # provide authentication via personal access tokens. Public repos work
194
+ # without credentials.
195
+
196
+ # [workspace_templates.default]
197
+ # repo = "https://github.com/org/repo.git" # git repo to clone into workspace
198
+ # tmuxinator = true # generate tmuxinator config
199
+ # inherits = [] # list of parent template names
200
+
201
+ # [workspace_templates.myproject]
202
+ # repo = "https://github.com/org/myproject.git"
203
+ # tmuxinator = false
204
+ # inherits = ["default"]
205
+
206
+ # --- Session templates ---
207
+ # Define commands that sessions can run. The built-in "default" template runs a
208
+ # login shell. Templates support inheritance via "inherits".
209
+ #
210
+ # Template commands support {{session_name}} and {{workspace_name}} variables,
211
+ # using double-brace syntax consistent with nerftools manifests.
212
+ #
213
+ # Templates may define a restart_command for `session restart`. If omitted,
214
+ # the regular command is used. This is useful for tools like Claude Code
215
+ # where --resume picks up the previous conversation.
216
+ #
217
+ # env maps are merged during inheritance (child wins on key collision).
218
+
219
+ # [session_templates.default] # override the built-in default
220
+ # command = "claude --name {{session_name}}"
221
+ # restart_command = "claude --resume {{session_name}}"
222
+ # description = "Claude Code interactive session"
223
+
224
+ # [session_templates.claude]
225
+ # inherits = ["default"]
226
+ # command = "claude --name {{session_name}}"
227
+ # restart_command = "claude --resume {{session_name}}"
228
+ # description = "Claude Code interactive session"
229
+
230
+ # [session_templates.custom-tool]
231
+ # command = "my-tool --flag"
232
+ # description = "Custom tool"
233
+ # env = { MY_VAR = "value" } # environment variables to set
234
+
235
+ # --- Session configuration ---
236
+ # Non-template session settings.
237
+
238
+ # [session.config]
239
+ # history_limit = 50000 # tmux scrollback buffer lines per session
240
+
241
+ # =============================================================================
242
+ # Catalog extensions
243
+ # =============================================================================
244
+ #
245
+ # Operator-defined entries extend the built-in catalog. Entries with the same name
246
+ # as a built-in entry override it. Run `agentworks installer list` to see all
247
+ # available entries (built-in and operator-defined).
248
+
249
+ # --- Custom apt sources ---
250
+ # Third-party apt repositories.
251
+ # Use {arch} as a placeholder for the VM architecture (amd64 or arm64).
252
+
253
+ # [apt_sources.my-repo]
254
+ # description = "My internal apt repository"
255
+ # key_url = "https://apt.example.com/key.gpg"
256
+ # key_path = "/etc/apt/keyrings/my-repo.gpg"
257
+ # source = "deb [arch={arch} signed-by=/etc/apt/keyrings/my-repo.gpg] https://apt.example.com/debian bookworm main"
258
+ # source_file = "my-repo.list"
259
+
260
+ # --- Custom apt packages ---
261
+ # Named package sets with optional apt source dependencies.
262
+
263
+ # [apt_packages.my-tool]
264
+ # description = "My internal tool"
265
+ # apt_sources = ["my-repo"]
266
+ # apt = ["my-tool"]
267
+
268
+ # --- Custom system install commands ---
269
+ # Shell commands that install system-wide tooling (run during vm init/reinit).
270
+ # These may be re-run (e.g. during reinit) so they should be idempotent.
271
+ # Optional checks short-circuit installation if the tool is already present.
272
+ # Use only one of: test_exec, test_file, or test_dir.
273
+
274
+ # [system_install_commands.my-system-tool]
275
+ # description = "Custom system-wide tool"
276
+ # command = "curl -fsSL https://example.com/install.sh | sudo bash"
277
+ # test_exec = "my-system-tool" # skip if command is on PATH
278
+
279
+ # --- Custom user install commands ---
280
+ # Shell commands that install per-user tooling (run for admin and agents).
281
+ # These may be re-run so they should be idempotent.
282
+ # Use only one of: test_exec, test_file, or test_dir.
283
+ # For test_file/test_dir, ~ resolves to the target user's home.
284
+
285
+ # [user_install_commands.my-tool]
286
+ # description = "Custom per-user tool"
287
+ # command = "curl -fsSL https://example.com/install.sh | bash"
288
+ # path = ["~/.my-tool/bin"]
289
+ # test_exec = "my-tool" # skip if command is on PATH
File without changes
@@ -0,0 +1,164 @@
1
+ """VM console management.
2
+
3
+ The console is a VM-level tmux session that provides a unified view of all
4
+ sessions running on the VM. It has full tmux controls (the operator can split
5
+ panes, create windows, rearrange layout). Each session appears as a window
6
+ that attaches to the session's locked-down tmux session.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import shlex
12
+ import sys
13
+ from typing import TYPE_CHECKING
14
+
15
+ from agentworks import output
16
+ from agentworks.sessions.tmux import tmux_cmd
17
+
18
+ if TYPE_CHECKING:
19
+ from agentworks.config import Config
20
+ from agentworks.db import Database, SessionRow, VMRow
21
+ from agentworks.sessions.tmux import RunCommand
22
+
23
+ CONSOLE_SESSION_NAME = "vm-console"
24
+
25
+
26
+ def console_exists(*, run_command: RunCommand) -> bool:
27
+ """Check if the console tmux session exists on the VM."""
28
+ result = run_command(f"tmux has-session -t {CONSOLE_SESSION_NAME} 2>/dev/null", check=False)
29
+ return getattr(result, "ok", False)
30
+
31
+
32
+ def create_console(
33
+ sessions: list[SessionRow],
34
+ *,
35
+ run_command: RunCommand,
36
+ admin_username: str,
37
+ recreate: bool = False,
38
+ ) -> None:
39
+ """Create the VM console session with one window per session.
40
+
41
+ When *recreate* is True, kills any existing console session first.
42
+ """
43
+ if recreate:
44
+ run_command(f"tmux kill-session -t {CONSOLE_SESSION_NAME}", check=False)
45
+
46
+ # Create the session with a login shell as the initial window
47
+ run_command(
48
+ f"tmux new-session -d -s {CONSOLE_SESSION_NAME} "
49
+ f"-n admin-shell "
50
+ f"{shlex.quote('exec sudo su --login ' + shlex.quote(admin_username))}"
51
+ )
52
+
53
+ # Keep windows open when attached session command exits
54
+ run_command(f"tmux set -t {CONSOLE_SESSION_NAME} remain-on-exit on", check=False)
55
+
56
+ # Add a window for each session (wrapper loop handles ended sessions)
57
+ output.info(f"Adding {len(sessions)} session(s) to console...")
58
+ for session in sessions:
59
+ _add_session_window(
60
+ session.name,
61
+ run_command=run_command,
62
+ socket_path=session.socket_path,
63
+ )
64
+
65
+
66
+ def _add_session_window(
67
+ session_name: str,
68
+ *,
69
+ run_command: RunCommand,
70
+ socket_path: str | None = None,
71
+ ) -> None:
72
+ """Add a single session window to the console."""
73
+ q_session = shlex.quote(session_name)
74
+ # Unset TMUX to allow nesting (console -> session), then loop
75
+ # re-attach while the session is alive.
76
+ has_cmd = tmux_cmd(f"has-session -t {q_session}", socket_path)
77
+ attach_cmd = tmux_cmd(f"attach -t {q_session}", socket_path)
78
+ wrapper = (
79
+ f"unset TMUX; "
80
+ f"while {has_cmd} 2>/dev/null; do "
81
+ f"{attach_cmd}; "
82
+ f"sleep 0.5; "
83
+ f"done; "
84
+ f"echo 'Session {q_session} has ended. Press enter to close.'; "
85
+ f"read"
86
+ )
87
+ result = run_command(
88
+ f"tmux new-window -t {CONSOLE_SESSION_NAME} -n {q_session} {shlex.quote(wrapper)}",
89
+ check=False,
90
+ )
91
+ ok = getattr(result, "ok", True)
92
+ stderr = getattr(result, "stderr", "")
93
+ if not ok:
94
+ output.warn(f"failed to add window for '{session_name}': {stderr}")
95
+
96
+
97
+ def add_session_to_console(
98
+ session_name: str,
99
+ *,
100
+ run_command: RunCommand,
101
+ socket_path: str | None = None,
102
+ ) -> None:
103
+ """Add a session window to an existing console (best-effort)."""
104
+ if not console_exists(run_command=run_command):
105
+ return
106
+
107
+ _add_session_window(session_name, run_command=run_command, socket_path=socket_path)
108
+
109
+
110
+ def attach_console(
111
+ db: Database,
112
+ config: Config,
113
+ *,
114
+ vm_name: str,
115
+ recreate: bool = False,
116
+ allow_nesting: bool = False,
117
+ ) -> None:
118
+ """Attach to (or create) the VM console."""
119
+ import os
120
+
121
+ if os.environ.get("TMUX") and not allow_nesting:
122
+ raise output.SessionError(
123
+ "already inside a tmux session.\n"
124
+ "Nesting is not recommended (prefix key conflicts,\n"
125
+ "confusing detach behavior).\n"
126
+ "Pass --allow-nesting to override."
127
+ )
128
+
129
+ vm = db.get_vm(vm_name)
130
+ if vm is None:
131
+ raise output.VMError(f"VM '{vm_name}' not found")
132
+
133
+ from agentworks.workspaces.manager import _ensure_vm_running
134
+
135
+ _ensure_vm_running(db, config, vm)
136
+
137
+ if vm.tailscale_host is None:
138
+ raise output.VMError(f"VM '{vm_name}' has no Tailscale address")
139
+
140
+ from agentworks.ssh import admin_exec_target, interactive
141
+
142
+ target = admin_exec_target(vm, config)
143
+
144
+ # Get sessions for this VM (console wrapper handles dead sessions)
145
+ vm_sessions = _get_sessions_for_vm(db, vm)
146
+
147
+ if recreate or not console_exists(run_command=target.run):
148
+ create_console(
149
+ vm_sessions,
150
+ run_command=target.run,
151
+ admin_username=vm.admin_username,
152
+ recreate=recreate,
153
+ )
154
+
155
+ sys.exit(interactive(target, f"tmux attach -t {CONSOLE_SESSION_NAME}"))
156
+
157
+
158
+ def _get_sessions_for_vm(db: Database, vm: VMRow) -> list[SessionRow]:
159
+ """Get all sessions across all workspaces on a VM."""
160
+ workspaces = db.list_workspaces(vm_name=vm.name)
161
+ sessions: list[SessionRow] = []
162
+ for ws in workspaces:
163
+ sessions.extend(db.list_sessions(workspace_name=ws.name))
164
+ return sessions