portacode 1.3.32__py3-none-any.whl → 1.4.11.dev0__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 (56) hide show
  1. portacode/_version.py +2 -2
  2. portacode/cli.py +119 -14
  3. portacode/connection/client.py +127 -8
  4. portacode/connection/handlers/WEBSOCKET_PROTOCOL.md +301 -4
  5. portacode/connection/handlers/__init__.py +10 -1
  6. portacode/connection/handlers/diff_handlers.py +603 -0
  7. portacode/connection/handlers/file_handlers.py +674 -17
  8. portacode/connection/handlers/project_aware_file_handlers.py +11 -0
  9. portacode/connection/handlers/project_state/file_system_watcher.py +31 -61
  10. portacode/connection/handlers/project_state/git_manager.py +139 -572
  11. portacode/connection/handlers/project_state/handlers.py +28 -14
  12. portacode/connection/handlers/project_state/manager.py +226 -101
  13. portacode/connection/handlers/proxmox_infra.py +307 -0
  14. portacode/connection/handlers/session.py +465 -84
  15. portacode/connection/handlers/system_handlers.py +140 -8
  16. portacode/connection/handlers/tab_factory.py +1 -47
  17. portacode/connection/handlers/update_handler.py +61 -0
  18. portacode/connection/terminal.py +51 -10
  19. portacode/keypair.py +63 -1
  20. portacode/link_capture/__init__.py +38 -0
  21. portacode/link_capture/__pycache__/__init__.cpython-311.pyc +0 -0
  22. portacode/link_capture/bin/__pycache__/link_capture_wrapper.cpython-311.pyc +0 -0
  23. portacode/link_capture/bin/elinks +3 -0
  24. portacode/link_capture/bin/gio-open +3 -0
  25. portacode/link_capture/bin/gnome-open +3 -0
  26. portacode/link_capture/bin/gvfs-open +3 -0
  27. portacode/link_capture/bin/kde-open +3 -0
  28. portacode/link_capture/bin/kfmclient +3 -0
  29. portacode/link_capture/bin/link_capture_exec.sh +11 -0
  30. portacode/link_capture/bin/link_capture_wrapper.py +75 -0
  31. portacode/link_capture/bin/links +3 -0
  32. portacode/link_capture/bin/links2 +3 -0
  33. portacode/link_capture/bin/lynx +3 -0
  34. portacode/link_capture/bin/mate-open +3 -0
  35. portacode/link_capture/bin/netsurf +3 -0
  36. portacode/link_capture/bin/sensible-browser +3 -0
  37. portacode/link_capture/bin/w3m +3 -0
  38. portacode/link_capture/bin/x-www-browser +3 -0
  39. portacode/link_capture/bin/xdg-open +3 -0
  40. portacode/pairing.py +103 -0
  41. portacode/static/js/utils/ntp-clock.js +170 -79
  42. portacode/utils/diff_apply.py +456 -0
  43. portacode/utils/diff_renderer.py +371 -0
  44. portacode/utils/ntp_clock.py +45 -131
  45. {portacode-1.3.32.dist-info → portacode-1.4.11.dev0.dist-info}/METADATA +71 -3
  46. portacode-1.4.11.dev0.dist-info/RECORD +97 -0
  47. test_modules/test_device_online.py +1 -1
  48. test_modules/test_login_flow.py +8 -4
  49. test_modules/test_play_store_screenshots.py +294 -0
  50. testing_framework/.env.example +4 -1
  51. testing_framework/core/playwright_manager.py +63 -9
  52. portacode-1.3.32.dist-info/RECORD +0 -70
  53. {portacode-1.3.32.dist-info → portacode-1.4.11.dev0.dist-info}/WHEEL +0 -0
  54. {portacode-1.3.32.dist-info → portacode-1.4.11.dev0.dist-info}/entry_points.txt +0 -0
  55. {portacode-1.3.32.dist-info → portacode-1.4.11.dev0.dist-info}/licenses/LICENSE +0 -0
  56. {portacode-1.3.32.dist-info → portacode-1.4.11.dev0.dist-info}/top_level.txt +0 -0
@@ -41,9 +41,14 @@ This document describes the complete protocol for communicating with devices thr
41
41
  - [`terminal_list`](#terminal_list)
42
42
  - [System Actions](#system-actions)
43
43
  - [`system_info`](#system_info)
44
+ - [`update_portacode_cli`](#update_portacode_cli)
45
+ - [`clock_sync_request`](#clock_sync_request)
44
46
  - [File Actions](#file-actions)
45
47
  - [`file_read`](#file_read)
48
+ - [`file_search`](#file_search)
46
49
  - [`file_write`](#file_write)
50
+ - [`file_apply_diff`](#file_apply_diff)
51
+ - [`file_preview_diff`](#file_preview_diff)
47
52
  - [`directory_list`](#directory_list)
48
53
  - [`file_info`](#file_info)
49
54
  - [`file_delete`](#file_delete)
@@ -78,9 +83,14 @@ This document describes the complete protocol for communicating with devices thr
78
83
  - [`terminal_list`](#terminal_list-event)
79
84
  - [System Events](#system-events)
80
85
  - [`system_info`](#system_info-event)
86
+ - [`update_portacode_response`](#update_portacode_response)
87
+ - [`clock_sync_response`](#clock_sync_response)
81
88
  - [File Events](#file-events)
82
89
  - [`file_read_response`](#file_read_response)
90
+ - [`file_search_response`](#file_search_response)
83
91
  - [`file_write_response`](#file_write_response)
92
+ - [`file_apply_diff_response`](#file_apply_diff_response)
93
+ - [`file_preview_diff_response`](#file_preview_diff_response)
84
94
  - [`directory_list_response`](#directory_list_response)
85
95
  - [`file_info_response`](#file_info_response)
86
96
  - [`file_delete_response`](#file_delete_response)
@@ -310,6 +320,46 @@ This action does not require any payload fields.
310
320
 
311
321
  * On success, the device will respond with a [`system_info`](#system_info-event) event.
312
322
 
323
+ ### `setup_proxmox_infra`
324
+
325
+ Configures a Proxmox node for Portacode infrastructure usage (API token storage, default storage selection, and bridge setup). Handled by [`ConfigureProxmoxInfraHandler`](./proxmox_infra.py).
326
+
327
+ **Payload Fields:**
328
+
329
+ * `token_identifier` (string, required): API token identifier in the form `user@realm!tokenid`.
330
+ * `token_value` (string, required): Secret value associated with the token.
331
+ * `verify_ssl` (boolean, optional): When true, the handler verifies SSL certificates; defaults to `false`.
332
+
333
+ **Responses:**
334
+
335
+ * On success, the device will emit a [`proxmox_infra_configured`](#proxmox_infra_configured-event) event with the persisted infra snapshot.
336
+ * On failure, the device will emit an [`error`](#error) event with details (e.g., permission issues, missing proxmoxer/dnsmasq, or missing root privileges).
337
+
338
+ ### `clock_sync_request`
339
+
340
+ Internal event that devices send to the gateway to request the authoritative server timestamp (used for adjusting `portacode.utils.ntp_clock`). The gateway responds immediately with [`clock_sync_response`](#clock_sync_response).
341
+
342
+ **Payload Fields:**
343
+
344
+ * `request_id` (string, optional): Correlates the response with the request.
345
+
346
+ **Responses:**
347
+
348
+ * The gateway responds with [`clock_sync_response`](#clock_sync_response) that includes the authoritative `server_time` (plus the optional `server_time_iso` mirror).
349
+
350
+ ### `update_portacode_cli`
351
+
352
+ Updates the Portacode CLI package and restarts the process. Handled by [`update_portacode_cli`](./update_handler.py).
353
+
354
+ **Payload Fields:**
355
+
356
+ This action does not require any payload fields.
357
+
358
+ **Responses:**
359
+
360
+ * On success, the device will respond with an `update_portacode_response` event and then exit with code 42 to trigger restart.
361
+ * On error, an `update_portacode_response` event with error details is sent.
362
+
313
363
  ### `file_read`
314
364
 
315
365
  Reads the content of a file. Handled by [`file_read`](./file_handlers.py).
@@ -317,12 +367,46 @@ Reads the content of a file. Handled by [`file_read`](./file_handlers.py).
317
367
  **Payload Fields:**
318
368
 
319
369
  * `path` (string, mandatory): The absolute path to the file to read.
370
+ * `start_line` (integer, optional): 1-based line number to start reading from. Defaults to `1`.
371
+ * `end_line` (integer, optional): 1-based line number to stop reading at (inclusive). When provided, limits the response to the range between `start_line` and `end_line`.
372
+ * `max_lines` (integer, optional): Maximum number of lines to return (capped at 2000). Useful for pagination when `end_line` is not specified.
373
+ * `encoding` (string, optional): Text encoding to use when reading the file. Defaults to `utf-8` with replacement for invalid bytes.
320
374
 
321
375
  **Responses:**
322
376
 
323
377
  * On success, the device will respond with a [`file_read_response`](#file_read_response) event.
324
378
  * On error, a generic [`error`](#error) event is sent.
325
379
 
380
+ ### `file_search`
381
+
382
+ Searches for text matches within files beneath a given root directory. Handled by [`file_search`](./file_handlers.py).
383
+
384
+ **Payload Fields:**
385
+
386
+ * `root_path` (string, mandatory): The absolute path that acts as the search root (typically a project folder).
387
+ * `query` (string, mandatory): The search query. Treated as plain text unless `regex=true`.
388
+ * `match_case` (boolean, optional): When `true`, performs a case-sensitive search. Defaults to `false`.
389
+ * `regex` (boolean, optional): When `true`, interprets `query` as a regular expression. Defaults to `false`.
390
+ * `whole_word` (boolean, optional): When `true`, matches only whole words. Works with both plain text and regex queries.
391
+ * `include_patterns` (array[string], optional): Glob patterns that files must match to be included (e.g., `["src/**/*.py"]`).
392
+ * `exclude_patterns` (array[string], optional): Glob patterns for files/directories to skip (e.g., `["**/tests/**"]`).
393
+ * `include_hidden` (boolean, optional): When `true`, includes hidden files and folders. Defaults to `false`.
394
+ * `max_results` (integer, optional): Maximum number of match entries to return (capped at 500). Defaults to `40`.
395
+ * `max_matches_per_file` (integer, optional): Maximum number of matches to return per file (capped at 50). Defaults to `5`.
396
+ * `max_file_size` (integer, optional): Maximum file size in bytes to scan (defaults to 1 MiB).
397
+ * `max_line_length` (integer, optional): Maximum number of characters to return per matching line (defaults to `200`).
398
+
399
+ **Default Behaviour:**
400
+
401
+ * Binary files and large vendor/static directories (e.g., `node_modules`, `dist`, `static`) are skipped automatically unless custom `exclude_patterns` are provided.
402
+ * Only common source/text extensions are scanned by default (override with `include_patterns` to widen the scope).
403
+ * Searches stop after 10 seconds, respecting both per-file and global match limits to avoid oversized responses.
404
+
405
+ **Responses:**
406
+
407
+ * On success, the device will respond with a [`file_search_response`](#file_search_response) event containing the matches.
408
+ * On error, a generic [`error`](#error) event is sent.
409
+
326
410
  ### `file_write`
327
411
 
328
412
  Writes content to a file. Handled by [`file_write`](./file_handlers.py).
@@ -335,6 +419,57 @@ Writes content to a file. Handled by [`file_write`](./file_handlers.py).
335
419
  **Responses:**
336
420
 
337
421
  * On success, the device will respond with a [`file_write_response`](#file_write_response) event.
422
+
423
+ ### `file_apply_diff`
424
+
425
+ Apply one or more unified diff hunks to local files. Handled by [`file_apply_diff`](./diff_handlers.py).
426
+
427
+ **Request Payload:**
428
+
429
+ ```json
430
+ {
431
+ "cmd": "file_apply_diff",
432
+ "diff": "<unified diff string>",
433
+ "base_path": "<optional base path for relative diff entries>",
434
+ "project_id": "<server project UUID>",
435
+ "source_client_session": "<originating session/channel>"
436
+ }
437
+ ```
438
+
439
+ **Behavior:**
440
+
441
+ * `diff` must be standard unified diff text (like `git diff` output). Multiple files per diff are supported.
442
+ * If `base_path` is omitted the handler will attempt to derive the active project root from `source_client_session`, falling back to the device working directory.
443
+ * Each file hunk is validated before writing; context mismatches or missing files return per-file errors without aborting the rest.
444
+ * `/dev/null` entries are interpreted as file creations/deletions.
445
+ * Inline directives are also supported on their own lines. Use `@@delete:relative/path.py@@` to delete a file directly or `@@move:old/path.py -> new/path.py@@` (alias `@@rename:...@@`) to move/rename a file without crafting a diff. Directives are evaluated before the diff hunks and must point to files inside the project base.
446
+
447
+ * On completion the device responds with [`file_apply_diff_response`](#file_apply_diff_response).
448
+ * On error, a generic [`error`](#error) event is sent.
449
+
450
+ ### `file_preview_diff`
451
+
452
+ Validate one or more unified diff hunks and render an HTML preview without mutating any files. Handled by [`file_preview_diff`](./diff_handlers.py).
453
+
454
+ **Request Payload:**
455
+
456
+ ```json
457
+ {
458
+ "cmd": "file_preview_diff",
459
+ "diff": "<unified diff string>",
460
+ "base_path": "<optional base path for relative diff entries>",
461
+ "request_id": "req_123456"
462
+ }
463
+ ```
464
+
465
+ **Behavior:**
466
+
467
+ * Reuses the same parser as `file_apply_diff`, so invalid hunks surface the same errors.
468
+ * Produces HTML snippets per file using the device-side renderer. No files are modified.
469
+ * Inline directives (`@@delete:...@@`, `@@move:src -> dest@@`) use the same syntax as `file_apply_diff`. The handler validates them up front and includes them in the preview output so the user can see deletions or moves before clicking “Apply”.
470
+ * Returns immediately with an error payload if preview generation fails.
471
+
472
+ * On completion the device responds with [`file_preview_diff_response`](#file_preview_diff_response).
338
473
  * On error, a generic [`error`](#error) event is sent.
339
474
 
340
475
  ### `directory_list`
@@ -345,6 +480,8 @@ Lists the contents of a directory. Handled by [`directory_list`](./file_handlers
345
480
 
346
481
  * `path` (string, optional): The path to the directory to list. Defaults to the current directory.
347
482
  * `show_hidden` (boolean, optional): Whether to include hidden files in the listing. Defaults to `false`.
483
+ * `limit` (integer, optional): Maximum number of entries to return (defaults to “all”). Values above 1000 are clamped to 1000.
484
+ * `offset` (integer, optional): Number of entries to skip before collecting results (defaults to `0`).
348
485
 
349
486
  **Responses:**
350
487
 
@@ -750,6 +887,22 @@ The `data` field contains the exact bytes output by the terminal process, decode
750
887
 
751
888
  **Security Note**: The `device_id` field is automatically injected by the server based on the authenticated connection - the device cannot and should not specify its own ID. The `project_id` and `client_sessions` fields are added by the device's terminal manager for proper routing and filtering.
752
889
 
890
+ ### <a name="terminal_link_request"></a>`terminal_link_request`
891
+
892
+ Signals that the active terminal session attempted to open an external URL (e.g., via `xdg-open`). The terminal environment is instrumented with the `portacode/link_capture` helper, so CLI programs that try to open a browser are captured and forwarded to connected clients for confirmation.
893
+
894
+ **Event Fields:**
895
+
896
+ * `terminal_id` (string, mandatory): The UUID of the terminal session that triggered the request.
897
+ * `channel` (string, mandatory): Same as `terminal_id` (included for backward compatibility with raw channel routing).
898
+ * `url` (string, mandatory): The full URL the terminal tried to open. Clients must surface this text directly so users can verify it.
899
+ * `command` (string, optional): The command that attempted the navigation (e.g., `xdg-open`).
900
+ * `args` (array[string], optional): Arguments passed to the command, which may include safely-encoded paths or flags.
901
+ * `timestamp` (number, optional): UNIX epoch seconds when the capture occurred.
902
+ * `project_id` (string, optional): The project UUID in whose context the attempt was made.
903
+
904
+ Clients receiving this event should pause and ask the user for confirmation before opening the URL, and may throttle or suppress repeated events to prevent modal storms if a CLI tool loops on the same link.
905
+
753
906
  ### <a name="terminal_exit"></a>`terminal_exit`
754
907
 
755
908
  Notifies the server that a terminal session has terminated. This can be due to the process ending or the session being stopped. Handled by [`terminal_start`](./terminal_handlers.py).
@@ -806,6 +959,76 @@ Provides system information in response to a `system_info` action. Handled by [`
806
959
  * `memory` (object): Memory usage statistics.
807
960
  * `disk` (object): Disk usage statistics.
808
961
  * `os_info` (object): Operating system details, including `os_type`, `os_version`, `architecture`, `default_shell`, and `default_cwd`.
962
+ * `user_context` (object): Information about the user running the CLI, including:
963
+ * `username` (string): Resolved username (via `os.getlogin` or fallback).
964
+ * `username_source` (string): Which API resolved the username.
965
+ * `home` (string): Home directory detected for the CLI user.
966
+ * `uid` (integer|null): POSIX UID when available.
967
+ * `euid` (integer|null): Effective UID when available.
968
+ * `is_root` (boolean|null): True when running as root/administrator.
969
+ * `has_sudo` (boolean): Whether a `sudo` binary exists on the host.
970
+ * `sudo_user` (string|null): Value of `SUDO_USER` when set.
971
+ * `is_sudo_session` (boolean): True when the CLI was started via `sudo`.
972
+ * `playwright` (object): Optional Playwright runtime metadata when Playwright is installed:
973
+ * `installed` (boolean): True if Playwright is importable on the device.
974
+ * `version` (string|null): Exact package version when available.
975
+ * `browsers` (object): Browser-specific data keyed by Playwright browser names:
976
+ * `<browser>` (object): Per-browser info (variants: `chromium`, `firefox`, `webkit`).
977
+ * `available` (boolean): True when Playwright knows an executable path.
978
+ * `executable_path` (string|null): Absolute path to the browser binary when known.
979
+ * `error` (string|null): Any warning message captured while probing Playwright.
980
+ * `proxmox` (object): Detection hints for Proxmox VE nodes:
981
+ * `is_proxmox_node` (boolean): True when Proxmox artifacts (e.g., `/etc/proxmox-release`) exist.
982
+ * `version` (string|null): Raw contents of `/etc/proxmox-release` when readable.
983
+ * `infra` (object): Portacode infrastructure configuration snapshot:
984
+ * `configured` (boolean): True when `setup_proxmox_infra` stored an API token.
985
+ * `host` (string|null): Hostname used for the API client (usually `localhost`).
986
+ * `node` (string|null): Proxmox node name that was targeted.
987
+ * `user` (string|null): API token owner (e.g., `root@pam`).
988
+ * `token_name` (string|null): API token identifier.
989
+ * `default_storage` (string|null): Storage pool chosen for future containers.
990
+ * `templates` (array[string]): Cached list of available LXC templates.
991
+ * `last_verified` (string|null): ISO timestamp when the token was last validated.
992
+ * `network` (object):
993
+ * `applied` (boolean): True when the bridge/NAT services were successfully configured.
994
+ * `message` (string|null): Informational text about the network setup attempt.
995
+ * `bridge` (string): The bridge interface configured (typically `vmbr1`).
996
+ * `node_status` (object|null): Status response returned by the Proxmox API when validating the token.
997
+ * `portacode_version` (string): Installed CLI version returned by `portacode.__version__`.
998
+
999
+ ### `proxmox_infra_configured`
1000
+
1001
+ Emitted after a successful `setup_proxmox_infra` action. The event reports the stored API token metadata, template list, and network setup status.
1002
+
1003
+ **Event Fields:**
1004
+
1005
+ * `success` (boolean): True when the configuration completed.
1006
+ * `message` (string): User-facing summary (e.g., "Proxmox infrastructure configured").
1007
+ * `infra` (object): Same snapshot described under [`system_info`](#system_info-event) `proxmox.infra`.
1008
+
1009
+ ### <a name="clock_sync_response"></a>`clock_sync_response`
1010
+
1011
+ Reply sent by the gateway immediately after receiving a `clock_sync_request`. Devices use this event plus the measured round-trip time to keep their local `ntp_clock` offset accurate.
1012
+
1013
+ **Event Fields:**
1014
+
1015
+ * `event` (string): Always `clock_sync_response`.
1016
+ * `server_time` (integer): Server time in milliseconds.
1017
+ * `server_time_iso` (string, optional): ISO 8601 representation of `server_time`, useful for UI dashboards.
1018
+ * `server_receive_time` (integer, optional): Timestamp when the gateway received the sync request.
1019
+ * `server_send_time` (integer, optional): Timestamp when the gateway replied; used to compute a midpoint for latency compensation.
1020
+ * `request_id` (string, optional): Mirrors the request's `request_id`.
1021
+
1022
+ ### `update_portacode_response`
1023
+
1024
+ Reports the result of an `update_portacode_cli` action. Handled by [`update_portacode_cli`](./update_handler.py).
1025
+
1026
+ **Event Fields:**
1027
+
1028
+ * `success` (boolean, mandatory): Whether the update operation was successful.
1029
+ * `message` (string, optional): Success message when update completes.
1030
+ * `error` (string, optional): Error message when update fails.
1031
+ * `restart_required` (boolean, optional): Indicates if process restart is required (always true for successful updates).
809
1032
 
810
1033
  ### <a name="file_read_response"></a>`file_read_response`
811
1034
 
@@ -814,8 +1037,39 @@ Returns the content of a file in response to a `file_read` action. Handled by [`
814
1037
  **Event Fields:**
815
1038
 
816
1039
  * `path` (string, mandatory): The path of the file that was read.
817
- * `content` (string, mandatory): The content of the file.
818
- * `size` (integer, mandatory): The size of the file in bytes.
1040
+ * `content` (string, mandatory): The file content returned (may be a slice when pagination parameters are used).
1041
+ * `size` (integer, mandatory): The total size of the file in bytes.
1042
+ * `total_lines` (integer, optional): Total number of lines detected in the file.
1043
+ * `returned_lines` (integer, optional): Number of lines included in `content`.
1044
+ * `start_line` (integer, optional): The first line number included in the response (if any lines were returned).
1045
+ * `requested_start_line` (integer, optional): The requested starting line supplied in the command.
1046
+ * `end_line` (integer, optional): The last line number included in the response.
1047
+ * `has_more_before` (boolean, optional): Whether there is additional content before the returned range.
1048
+ * `has_more_after` (boolean, optional): Whether there is additional content after the returned range.
1049
+ * `encoding` (string, optional): Encoding that was used while reading the file.
1050
+
1051
+ ### <a name="file_search_response"></a>`file_search_response`
1052
+
1053
+ Returns aggregated search results in response to a `file_search` action. Handled by [`file_search`](./file_handlers.py).
1054
+
1055
+ **Event Fields:**
1056
+
1057
+ * `root_path` (string, mandatory): The root directory that was searched.
1058
+ * `query` (string, mandatory): The query string that was used.
1059
+ * `match_case` (boolean, mandatory): Indicates if the search was case sensitive.
1060
+ * `regex` (boolean, mandatory): Indicates if the query was interpreted as a regular expression.
1061
+ * `whole_word` (boolean, mandatory): Indicates if the search matched whole words only.
1062
+ * `include_patterns` (array[string], mandatory): Effective include glob patterns.
1063
+ * `exclude_patterns` (array[string], mandatory): Effective exclude glob patterns.
1064
+ * `matches` (array, mandatory): List of match objects containing `relative_path`, `path`, `line_number`, `line`, `match_spans` `[start, end]`, `match_count`, and `line_truncated` (boolean).
1065
+ * `matches_returned` (integer, mandatory): Number of match entries returned (length of `matches`).
1066
+ * `total_matches` (integer, mandatory): Total number of matches found while scanning.
1067
+ * `files_scanned` (integer, mandatory): Count of files inspected.
1068
+ * `truncated` (boolean, mandatory): Indicates if additional matches exist beyond those returned.
1069
+ * `truncated_count` (integer, optional): Number of matches that were omitted due to truncation limits.
1070
+ * `max_results` (integer, mandatory): Maximum number of matches requested.
1071
+ * `max_matches_per_file` (integer, mandatory): Maximum matches requested per file.
1072
+ * `errors` (array[string], optional): Non-fatal errors encountered during scanning (e.g., unreadable files).
819
1073
 
820
1074
  ### <a name="file_write_response"></a>`file_write_response`
821
1075
 
@@ -827,6 +1081,45 @@ Confirms that a file has been written successfully in response to a `file_write`
827
1081
  * `bytes_written` (integer, mandatory): The number of bytes written to the file.
828
1082
  * `success` (boolean, mandatory): Indicates whether the write operation was successful.
829
1083
 
1084
+ ### <a name="file_apply_diff_response"></a>`file_apply_diff_response`
1085
+
1086
+ Reports the outcome of a [`file_apply_diff`](#file_apply_diff) action.
1087
+
1088
+ **Event Fields:**
1089
+
1090
+ * `event`: Always `"file_apply_diff_response"`.
1091
+ * `success`: Boolean indicating whether all hunks succeeded.
1092
+ * `status`: `"success"`, `"partial_failure"`, or `"failed"`.
1093
+ * `base_path`: Absolute base path used for relative diff entries.
1094
+ * `files_changed`: Number of files successfully updated.
1095
+ * `results`: Array containing one object per file with:
1096
+ * `path`: Absolute path on the device.
1097
+ * `status`: `"applied"` or `"error"`.
1098
+ * `action`: `"created"`, `"modified"`, or `"deleted"` (present for successes).
1099
+ * `bytes_written`: Bytes written for the file (0 for deletes).
1100
+ * `error`: Error text when the patch failed for that file.
1101
+ * `line`: Optional line number hint for mismatches.
1102
+
1103
+ The response is emitted even if some files fail so the caller can retry with corrected diffs.
1104
+
1105
+ ### <a name="file_preview_diff_response"></a>`file_preview_diff_response`
1106
+
1107
+ Reports the outcome of a [`file_preview_diff`](#file_preview_diff) action.
1108
+
1109
+ **Event Fields:**
1110
+
1111
+ * `event`: Always `"file_preview_diff_response"`.
1112
+ * `success`: Boolean indicating whether all previews rendered successfully.
1113
+ * `status`: `"success"`, `"partial_failure"`, or `"failed"`.
1114
+ * `base_path`: Absolute base path used for relative paths.
1115
+ * `previews`: Array containing one entry per file with:
1116
+ * `path`: Absolute path hint (used for syntax highlighting).
1117
+ * `relative_path`: Relative project path if known.
1118
+ * `status`: `"ready"` or `"error"`.
1119
+ * `html`: Rendered diff snippet (when status is `"ready"`).
1120
+ * `error`: Error text (when status is `"error"`).
1121
+ * `error`: Optional top-level error string when the entire preview failed (e.g., diff parse error).
1122
+
830
1123
  ### <a name="directory_list_response"></a>`directory_list_response`
831
1124
 
832
1125
  Returns the contents of a directory in response to a `directory_list` action. Handled by [`directory_list`](./file_handlers.py).
@@ -835,7 +1128,11 @@ Returns the contents of a directory in response to a `directory_list` action. Ha
835
1128
 
836
1129
  * `path` (string, mandatory): The path of the directory that was listed.
837
1130
  * `items` (array, mandatory): A list of objects, each representing a file or directory in the listed directory.
838
- * `count` (integer, mandatory): The number of items in the `items` list.
1131
+ * `count` (integer, mandatory): The number of items returned in this response (honours `limit`/`offset`).
1132
+ * `total_count` (integer, mandatory): Total number of entries in the directory before pagination.
1133
+ * `offset` (integer, optional): Offset that was applied.
1134
+ * `limit` (integer, optional): Limit that was applied (or `null` if none).
1135
+ * `has_more` (boolean, optional): Indicates whether additional items remain beyond the returned slice.
839
1136
 
840
1137
  ### <a name="file_info_response"></a>`file_info_response`
841
1138
 
@@ -1277,4 +1574,4 @@ Sent by the server to clients to provide initial device list snapshot.
1277
1574
 
1278
1575
  **Event Fields:**
1279
1576
 
1280
- * `devices` (array, mandatory): Array of device objects with status information
1577
+ * `devices` (array, mandatory): Array of device objects with status information
@@ -23,8 +23,10 @@ from .file_handlers import (
23
23
  FileCreateHandler,
24
24
  FolderCreateHandler,
25
25
  FileRenameHandler,
26
+ FileSearchHandler,
26
27
  ContentRequestHandler,
27
28
  )
29
+ from .diff_handlers import FileApplyDiffHandler, FilePreviewDiffHandler
28
30
  from .project_state_handlers import (
29
31
  ProjectStateFolderExpandHandler,
30
32
  ProjectStateFolderCollapseHandler,
@@ -38,6 +40,8 @@ from .project_state_handlers import (
38
40
  ProjectStateGitRevertHandler,
39
41
  ProjectStateGitCommitHandler,
40
42
  )
43
+ from .update_handler import UpdatePortacodeHandler
44
+ from .proxmox_infra import ConfigureProxmoxInfraHandler
41
45
 
42
46
  __all__ = [
43
47
  "BaseHandler",
@@ -49,6 +53,7 @@ __all__ = [
49
53
  "TerminalStopHandler",
50
54
  "TerminalListHandler",
51
55
  "SystemInfoHandler",
56
+ "ConfigureProxmoxInfraHandler",
52
57
  # File operation handlers (optional - register as needed)
53
58
  "FileReadHandler",
54
59
  "FileWriteHandler",
@@ -58,7 +63,10 @@ __all__ = [
58
63
  "FileCreateHandler",
59
64
  "FolderCreateHandler",
60
65
  "FileRenameHandler",
66
+ "FileSearchHandler",
61
67
  "ContentRequestHandler",
68
+ "FileApplyDiffHandler",
69
+ "FilePreviewDiffHandler",
62
70
  # Project state handlers
63
71
  "ProjectStateFolderExpandHandler",
64
72
  "ProjectStateFolderCollapseHandler",
@@ -71,4 +79,5 @@ __all__ = [
71
79
  "ProjectStateGitUnstageHandler",
72
80
  "ProjectStateGitRevertHandler",
73
81
  "ProjectStateGitCommitHandler",
74
- ]
82
+ "UpdatePortacodeHandler",
83
+ ]