amigent 1.0.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 (86) hide show
  1. amigent-1.0.0/LICENSE +21 -0
  2. amigent-1.0.0/PKG-INFO +138 -0
  3. amigent-1.0.0/README.md +110 -0
  4. amigent-1.0.0/pyproject.toml +53 -0
  5. amigent-1.0.0/setup.cfg +4 -0
  6. amigent-1.0.0/src/amigent/__init__.py +3 -0
  7. amigent-1.0.0/src/amigent/amiga/amigent-repair +65 -0
  8. amigent-1.0.0/src/amigent/amiga/amigent-repair.info +0 -0
  9. amigent-1.0.0/src/amigent/amiga/amigent-scan +9 -0
  10. amigent-1.0.0/src/amigent/amiga/bin/Amigent.info +0 -0
  11. amigent-1.0.0/src/amigent/amiga/bin/amigentd +0 -0
  12. amigent-1.0.0/src/amigent/amiga/bin/amigentd.000 +0 -0
  13. amigent-1.0.0/src/amigent/amiga/bin/amigentd.info +0 -0
  14. amigent-1.0.0/src/amigent/amiga/menu-add.rexx +45 -0
  15. amigent-1.0.0/src/amigent/amigactl.py +199 -0
  16. amigent-1.0.0/src/amigent/amigentd.py +466 -0
  17. amigent-1.0.0/src/amigent/channel.py +411 -0
  18. amigent-1.0.0/src/amigent/client.py +26 -0
  19. amigent-1.0.0/src/amigent/desktop.py +308 -0
  20. amigent-1.0.0/src/amigent/doctor.py +70 -0
  21. amigent-1.0.0/src/amigent/errors.py +11 -0
  22. amigent-1.0.0/src/amigent/files.py +236 -0
  23. amigent-1.0.0/src/amigent/fleet.py +252 -0
  24. amigent-1.0.0/src/amigent/inventory.py +37 -0
  25. amigent-1.0.0/src/amigent/legacy.py +90 -0
  26. amigent-1.0.0/src/amigent/locks.py +44 -0
  27. amigent-1.0.0/src/amigent/lzh.py +175 -0
  28. amigent-1.0.0/src/amigent/machines.py +126 -0
  29. amigent-1.0.0/src/amigent/macos/Amigent.swift +76 -0
  30. amigent-1.0.0/src/amigent/macos/Amigent.zip +0 -0
  31. amigent-1.0.0/src/amigent/net.py +29 -0
  32. amigent-1.0.0/src/amigent/paths.py +18 -0
  33. amigent-1.0.0/src/amigent/pictures.py +104 -0
  34. amigent-1.0.0/src/amigent/platform_util.py +22 -0
  35. amigent-1.0.0/src/amigent/power.py +66 -0
  36. amigent-1.0.0/src/amigent/roster.py +163 -0
  37. amigent-1.0.0/src/amigent/secret.py +73 -0
  38. amigent-1.0.0/src/amigent/serve.py +84 -0
  39. amigent-1.0.0/src/amigent/server.py +723 -0
  40. amigent-1.0.0/src/amigent/setup.py +177 -0
  41. amigent-1.0.0/src/amigent/tls.py +150 -0
  42. amigent-1.0.0/src/amigent/transfer.py +64 -0
  43. amigent-1.0.0/src/amigent/viewer.py +911 -0
  44. amigent-1.0.0/src/amigent/watch.py +71 -0
  45. amigent-1.0.0/src/amigent/web/amigent.icns +0 -0
  46. amigent-1.0.0/src/amigent/web/amigent.ico +0 -0
  47. amigent-1.0.0/src/amigent/web/fleet.css +22 -0
  48. amigent-1.0.0/src/amigent/web/fleet.html +29 -0
  49. amigent-1.0.0/src/amigent/web/fleet.js +158 -0
  50. amigent-1.0.0/src/amigent/web/icon.png +0 -0
  51. amigent-1.0.0/src/amigent/web/icon.svg +15 -0
  52. amigent-1.0.0/src/amigent/web/index.html +246 -0
  53. amigent-1.0.0/src/amigent/web/locales/cs.json +129 -0
  54. amigent-1.0.0/src/amigent/web/locales/da.json +129 -0
  55. amigent-1.0.0/src/amigent/web/locales/de.json +129 -0
  56. amigent-1.0.0/src/amigent/web/locales/en.json +129 -0
  57. amigent-1.0.0/src/amigent/web/locales/es.json +129 -0
  58. amigent-1.0.0/src/amigent/web/locales/fi.json +129 -0
  59. amigent-1.0.0/src/amigent/web/locales/fr.json +129 -0
  60. amigent-1.0.0/src/amigent/web/locales/hu.json +129 -0
  61. amigent-1.0.0/src/amigent/web/locales/it.json +129 -0
  62. amigent-1.0.0/src/amigent/web/locales/pl.json +129 -0
  63. amigent-1.0.0/src/amigent/web/locales/sk.json +129 -0
  64. amigent-1.0.0/src/amigent/web/locales/sv.json +129 -0
  65. amigent-1.0.0/src/amigent/web/viewer.css +448 -0
  66. amigent-1.0.0/src/amigent/web/viewer.js +939 -0
  67. amigent-1.0.0/src/amigent.egg-info/PKG-INFO +138 -0
  68. amigent-1.0.0/src/amigent.egg-info/SOURCES.txt +84 -0
  69. amigent-1.0.0/src/amigent.egg-info/dependency_links.txt +1 -0
  70. amigent-1.0.0/src/amigent.egg-info/entry_points.txt +6 -0
  71. amigent-1.0.0/src/amigent.egg-info/requires.txt +2 -0
  72. amigent-1.0.0/src/amigent.egg-info/top_level.txt +1 -0
  73. amigent-1.0.0/tests/test_amigactl.py +119 -0
  74. amigent-1.0.0/tests/test_amigentd.py +79 -0
  75. amigent-1.0.0/tests/test_channel.py +217 -0
  76. amigent-1.0.0/tests/test_errors.py +24 -0
  77. amigent-1.0.0/tests/test_fast.py +218 -0
  78. amigent-1.0.0/tests/test_fleet.py +66 -0
  79. amigent-1.0.0/tests/test_inventory.py +26 -0
  80. amigent-1.0.0/tests/test_lzh.py +22 -0
  81. amigent-1.0.0/tests/test_machines.py +93 -0
  82. amigent-1.0.0/tests/test_net.py +41 -0
  83. amigent-1.0.0/tests/test_power.py +61 -0
  84. amigent-1.0.0/tests/test_protocol2.py +279 -0
  85. amigent-1.0.0/tests/test_secret.py +30 -0
  86. amigent-1.0.0/tests/test_viewer.py +187 -0
amigent-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andreas Wendorf (Akustikrausch)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
amigent-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,138 @@
1
+ Metadata-Version: 2.4
2
+ Name: amigent
3
+ Version: 1.0.0
4
+ Summary: Complete remote control for a real Amiga, by AI or manually
5
+ Author: Andreas Wendorf (Akustikrausch)
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/akustikrausch/amigent
8
+ Project-URL: Documentation, https://github.com/akustikrausch/amigent/blob/main/docs/GUIDE.md
9
+ Project-URL: Changelog, https://github.com/akustikrausch/amigent/blob/main/CHANGELOG.md
10
+ Project-URL: Issues, https://github.com/akustikrausch/amigent/issues
11
+ Keywords: amiga,amigaos,mcp,remote-desktop,retrocomputing,ai-agent
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Topic :: System :: Emulators
21
+ Classifier: Topic :: System :: Networking
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: mcp<2,>=1.2
26
+ Requires-Dist: pillow>=10
27
+ Dynamic: license-file
28
+
29
+ ```
30
+ _____ .__ __
31
+ / _ \ _____ |__| ____ ____ ____ _/ |_
32
+ / /_\ \ / \ | | / ___\ _/ __ \ / \ \ __\
33
+ / | \| Y Y \| | / /_/ >\ ___/ | | \ | |
34
+ \____|__ /|__|_| /|__| \___ / \___ >|___| / |__|
35
+ \/ \/ /_____/ \/ \/
36
+
37
+ complete remote control for a real Amiga, by AI or manually
38
+ ```
39
+
40
+ <p align="center"><img src="https://raw.githubusercontent.com/akustikrausch/amigent/main/src/amigent/web/icon.png" width="96" alt="Amigent icon"></p>
41
+
42
+ Amigent connects your PC or Mac to your real Amiga over the home network. Then you can:
43
+
44
+ - **let an AI agent do the work**, for example Claude Code or Codex: install
45
+ software, update programs, change settings, copy files, click through
46
+ programs. Just tell it what you want.
47
+ - **use the Amiga yourself from your computer**, as a remote desktop like
48
+ TeamViewer or VNC: a window with the live screen, your own mouse and
49
+ keyboard, file transfer and a shared clipboard.
50
+
51
+ Both at the same time: you see every click the agent makes and can take over at
52
+ any moment.
53
+
54
+ ![Amigent with a Workbench screen and the keyboard panel](https://raw.githubusercontent.com/akustikrausch/amigent/main/docs/screenshots/viewer.png)
55
+
56
+ ## Installation
57
+
58
+ ### On the Amiga
59
+
60
+ **1.** Copy [`amigentd`](https://github.com/akustikrausch/amigent/blob/main/src/amigent/amiga/bin/amigentd) to the Amiga (`amigentd.000` on a
61
+ 68000), or unpack the archive from the
62
+ [releases](https://github.com/akustikrausch/amigent/releases).
63
+
64
+ **2.** Start it with `amigentd SETUP` in a Shell, or double-click its icon.
65
+
66
+ ### On the PC or Mac
67
+
68
+ **3.** Paste this into your AI agent:
69
+
70
+ Install Amigent for me: run "pipx install amigent" (if pipx or Python 3.10
71
+ or newer is missing, install them first: Windows winget, macOS Homebrew,
72
+ Linux the package manager). Then run amigent-setup; it finds my Amiga on
73
+ the network by itself. Register the MCP server "amigent" with the command
74
+ amigent-server, and run amigent-viewer --install so the remote desktop
75
+ shows up as a program. Tell me if you need to be restarted.
76
+
77
+ The agent sets up the Amiga over the network; from then on amigentd starts
78
+ with it.
79
+
80
+ **Windows:** Amigent appears in the Start menu. For an encrypted connection
81
+ `openssl` is needed, which comes with Git for Windows.
82
+
83
+ **macOS:** Amigent appears in `~/Applications`. On its first start, allow it
84
+ access to the local network, or it cannot reach the Amiga. Cmd is the right
85
+ Amiga key.
86
+
87
+ **Linux:** there is no program entry; start the remote desktop with
88
+ `amigent-viewer`.
89
+
90
+ Without an AI agent: see the [manual installation](https://github.com/akustikrausch/amigent/blob/main/docs/GUIDE.md#manual-installation).
91
+
92
+ ## What you need
93
+
94
+ - An Amiga with AmigaOS 3.x, any CPU from the 68000 on, a network connection
95
+ (a plipbox on the parallel port is enough) and a TCP/IP stack such as Roadshow.
96
+ The faster the CPU, the smoother the screen.
97
+ - A PC or Mac with Windows, macOS or Linux and Python 3.10 or newer.
98
+
99
+ ## What you can ask for
100
+
101
+ "Check which programs on the Amiga are outdated."
102
+ "Install the game from my Downloads folder and add it to the Games menu."
103
+ "Copy the demo to the Amiga and start it."
104
+ "Open the remote desktop."
105
+
106
+ ## Good to know
107
+
108
+ - **Slow networks are fine.** Only the parts of the screen that change are sent.
109
+ - **New address after a restart?** Amigent finds the Amiga again by itself. It
110
+ takes only the one that proves to be yours.
111
+ - **Several Amigas?** `amigent-fleet` shows them side by side with a preview.
112
+ - **After a big OS update**, `amigent-repair` puts back what the update removed.
113
+ - **Safe at home.** A key keeps other computers on the network out, and the
114
+ connection can be encrypted. Don't open the Amiga to the internet.
115
+
116
+ More in the [guide](https://github.com/akustikrausch/amigent/blob/main/docs/GUIDE.md) and the [FAQ](https://github.com/akustikrausch/amigent/blob/main/docs/FAQ.md).
117
+
118
+ ## Other projects
119
+
120
+ - [amimcp](https://github.com/thomas-luebker/amimcp) by Thomas Luebker also lets
121
+ an AI agent control a real Amiga, from OS 2.04 on, with amifleet as a native
122
+ Mac app for several machines. Use it if your Amiga runs OS 2.x or you want
123
+ nothing but Python's standard library on the PC.
124
+ - [MCP-AmigaOS4](https://github.com/derfsss/MCP-AmigaOS4) is for AmigaOS 4.1.
125
+ - For emulators such as WinUAE and Amiberry there are MCP servers of their own.
126
+
127
+ Amigent is the one to pick if you want to use the Amiga yourself as well, have a
128
+ slow network, or want help keeping the machine in shape.
129
+
130
+ ## Thanks
131
+
132
+ tbdye for amigactld, Volker Barthelmann for vbcc, and the PiStorm, Emu68, AmiKit
133
+ and Aminet people.
134
+
135
+ ## License
136
+
137
+ MIT, see [LICENSE](https://github.com/akustikrausch/amigent/blob/main/LICENSE). By Andreas Wendorf (Akustikrausch).
138
+ Changes are in the [changelog](https://github.com/akustikrausch/amigent/blob/main/CHANGELOG.md).
@@ -0,0 +1,110 @@
1
+ ```
2
+ _____ .__ __
3
+ / _ \ _____ |__| ____ ____ ____ _/ |_
4
+ / /_\ \ / \ | | / ___\ _/ __ \ / \ \ __\
5
+ / | \| Y Y \| | / /_/ >\ ___/ | | \ | |
6
+ \____|__ /|__|_| /|__| \___ / \___ >|___| / |__|
7
+ \/ \/ /_____/ \/ \/
8
+
9
+ complete remote control for a real Amiga, by AI or manually
10
+ ```
11
+
12
+ <p align="center"><img src="https://raw.githubusercontent.com/akustikrausch/amigent/main/src/amigent/web/icon.png" width="96" alt="Amigent icon"></p>
13
+
14
+ Amigent connects your PC or Mac to your real Amiga over the home network. Then you can:
15
+
16
+ - **let an AI agent do the work**, for example Claude Code or Codex: install
17
+ software, update programs, change settings, copy files, click through
18
+ programs. Just tell it what you want.
19
+ - **use the Amiga yourself from your computer**, as a remote desktop like
20
+ TeamViewer or VNC: a window with the live screen, your own mouse and
21
+ keyboard, file transfer and a shared clipboard.
22
+
23
+ Both at the same time: you see every click the agent makes and can take over at
24
+ any moment.
25
+
26
+ ![Amigent with a Workbench screen and the keyboard panel](https://raw.githubusercontent.com/akustikrausch/amigent/main/docs/screenshots/viewer.png)
27
+
28
+ ## Installation
29
+
30
+ ### On the Amiga
31
+
32
+ **1.** Copy [`amigentd`](https://github.com/akustikrausch/amigent/blob/main/src/amigent/amiga/bin/amigentd) to the Amiga (`amigentd.000` on a
33
+ 68000), or unpack the archive from the
34
+ [releases](https://github.com/akustikrausch/amigent/releases).
35
+
36
+ **2.** Start it with `amigentd SETUP` in a Shell, or double-click its icon.
37
+
38
+ ### On the PC or Mac
39
+
40
+ **3.** Paste this into your AI agent:
41
+
42
+ Install Amigent for me: run "pipx install amigent" (if pipx or Python 3.10
43
+ or newer is missing, install them first: Windows winget, macOS Homebrew,
44
+ Linux the package manager). Then run amigent-setup; it finds my Amiga on
45
+ the network by itself. Register the MCP server "amigent" with the command
46
+ amigent-server, and run amigent-viewer --install so the remote desktop
47
+ shows up as a program. Tell me if you need to be restarted.
48
+
49
+ The agent sets up the Amiga over the network; from then on amigentd starts
50
+ with it.
51
+
52
+ **Windows:** Amigent appears in the Start menu. For an encrypted connection
53
+ `openssl` is needed, which comes with Git for Windows.
54
+
55
+ **macOS:** Amigent appears in `~/Applications`. On its first start, allow it
56
+ access to the local network, or it cannot reach the Amiga. Cmd is the right
57
+ Amiga key.
58
+
59
+ **Linux:** there is no program entry; start the remote desktop with
60
+ `amigent-viewer`.
61
+
62
+ Without an AI agent: see the [manual installation](https://github.com/akustikrausch/amigent/blob/main/docs/GUIDE.md#manual-installation).
63
+
64
+ ## What you need
65
+
66
+ - An Amiga with AmigaOS 3.x, any CPU from the 68000 on, a network connection
67
+ (a plipbox on the parallel port is enough) and a TCP/IP stack such as Roadshow.
68
+ The faster the CPU, the smoother the screen.
69
+ - A PC or Mac with Windows, macOS or Linux and Python 3.10 or newer.
70
+
71
+ ## What you can ask for
72
+
73
+ "Check which programs on the Amiga are outdated."
74
+ "Install the game from my Downloads folder and add it to the Games menu."
75
+ "Copy the demo to the Amiga and start it."
76
+ "Open the remote desktop."
77
+
78
+ ## Good to know
79
+
80
+ - **Slow networks are fine.** Only the parts of the screen that change are sent.
81
+ - **New address after a restart?** Amigent finds the Amiga again by itself. It
82
+ takes only the one that proves to be yours.
83
+ - **Several Amigas?** `amigent-fleet` shows them side by side with a preview.
84
+ - **After a big OS update**, `amigent-repair` puts back what the update removed.
85
+ - **Safe at home.** A key keeps other computers on the network out, and the
86
+ connection can be encrypted. Don't open the Amiga to the internet.
87
+
88
+ More in the [guide](https://github.com/akustikrausch/amigent/blob/main/docs/GUIDE.md) and the [FAQ](https://github.com/akustikrausch/amigent/blob/main/docs/FAQ.md).
89
+
90
+ ## Other projects
91
+
92
+ - [amimcp](https://github.com/thomas-luebker/amimcp) by Thomas Luebker also lets
93
+ an AI agent control a real Amiga, from OS 2.04 on, with amifleet as a native
94
+ Mac app for several machines. Use it if your Amiga runs OS 2.x or you want
95
+ nothing but Python's standard library on the PC.
96
+ - [MCP-AmigaOS4](https://github.com/derfsss/MCP-AmigaOS4) is for AmigaOS 4.1.
97
+ - For emulators such as WinUAE and Amiberry there are MCP servers of their own.
98
+
99
+ Amigent is the one to pick if you want to use the Amiga yourself as well, have a
100
+ slow network, or want help keeping the machine in shape.
101
+
102
+ ## Thanks
103
+
104
+ tbdye for amigactld, Volker Barthelmann for vbcc, and the PiStorm, Emu68, AmiKit
105
+ and Aminet people.
106
+
107
+ ## License
108
+
109
+ MIT, see [LICENSE](https://github.com/akustikrausch/amigent/blob/main/LICENSE). By Andreas Wendorf (Akustikrausch).
110
+ Changes are in the [changelog](https://github.com/akustikrausch/amigent/blob/main/CHANGELOG.md).
@@ -0,0 +1,53 @@
1
+ [project]
2
+ name = "amigent"
3
+ dynamic = ["version"]
4
+ description = "Complete remote control for a real Amiga, by AI or manually"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [{ name = "Andreas Wendorf (Akustikrausch)" }]
10
+ keywords = ["amiga", "amigaos", "mcp", "remote-desktop", "retrocomputing", "ai-agent"]
11
+ classifiers = [
12
+ "Development Status :: 5 - Production/Stable",
13
+ "Environment :: Console",
14
+ "Intended Audience :: End Users/Desktop",
15
+ "Operating System :: Microsoft :: Windows",
16
+ "Operating System :: MacOS",
17
+ "Operating System :: POSIX :: Linux",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Topic :: System :: Emulators",
21
+ "Topic :: System :: Networking",
22
+ ]
23
+ dependencies = ["mcp>=1.2,<2", "pillow>=10"]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/akustikrausch/amigent"
27
+ Documentation = "https://github.com/akustikrausch/amigent/blob/main/docs/GUIDE.md"
28
+ Changelog = "https://github.com/akustikrausch/amigent/blob/main/CHANGELOG.md"
29
+ Issues = "https://github.com/akustikrausch/amigent/issues"
30
+
31
+ [project.scripts]
32
+ amigent-server = "amigent.server:main"
33
+ amigent-doctor = "amigent.doctor:main"
34
+ amigent-setup = "amigent.setup:main"
35
+ amigent-viewer = "amigent.viewer:main"
36
+ amigent-fleet = "amigent.fleet:main"
37
+
38
+ [build-system]
39
+ requires = ["setuptools>=77"]
40
+ build-backend = "setuptools.build_meta"
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+
45
+ [tool.setuptools.dynamic]
46
+ version = { attr = "amigent.__version__" }
47
+
48
+ [tool.setuptools.package-data]
49
+ amigent = ["web/*", "web/locales/*", "macos/*", "amiga/*", "amiga/bin/*"]
50
+
51
+ [tool.pytest.ini_options]
52
+ pythonpath = ["src"]
53
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """Amigent: complete remote control for a real Amiga, by AI or manually."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,65 @@
1
+ ; amigent-repair: restores Amigent after a system update. Safe to run twice.
2
+ FailAt 21
3
+ ; amigent-setup puts its folder here (SYS:amigent unless --home says otherwise)
4
+ Set SRC SYS:amigent
5
+ ; amigactld only where an older setup installed it
6
+ If EXISTS $SRC/amigactld
7
+ If NOT EXISTS C:amigactld
8
+ Copy $SRC/amigactld C: CLONE
9
+ Protect C:amigactld rwed
10
+ EndIf
11
+ EndIf
12
+ If NOT EXISTS C:amigentd
13
+ Copy $SRC/amigentd C: CLONE
14
+ Protect C:amigentd rwed
15
+ EndIf
16
+ If NOT EXISTS S:amigent.key
17
+ If EXISTS $SRC/amigent.key
18
+ Copy $SRC/amigent.key S: CLONE
19
+ EndIf
20
+ EndIf
21
+ If NOT EXISTS S:amigent-tls.pem
22
+ If EXISTS $SRC/amigent-tls.pem
23
+ Copy $SRC/amigent-tls.pem S: CLONE
24
+ EndIf
25
+ EndIf
26
+ If NOT EXISTS S:amigactld.conf
27
+ If EXISTS $SRC/amigactld.conf
28
+ Copy $SRC/amigactld.conf S: CLONE
29
+ EndIf
30
+ EndIf
31
+ Delete >NIL: S:amigent-start QUIET
32
+ If EXISTS $SRC/amigactld
33
+ Search >NIL: S:User-Startup "C:amigactld" QUIET
34
+ If WARN
35
+ Echo >>S:User-Startup "RUN >NIL: C:amigactld"
36
+ EndIf
37
+ EndIf
38
+ Search >NIL: S:User-Startup "C:amigentd" QUIET
39
+ If WARN
40
+ If EXISTS $SRC/amigent.tlsonly
41
+ Echo >>S:User-Startup "RUN >NIL: C:amigentd TLSONLY"
42
+ Else
43
+ Echo >>S:User-Startup "RUN >NIL: C:amigentd"
44
+ EndIf
45
+ EndIf
46
+ If EXISTS C:amigactld
47
+ Status >NIL: COMMAND=C:amigactld
48
+ If WARN
49
+ Run >NIL: C:amigactld
50
+ EndIf
51
+ EndIf
52
+ Status >NIL: COMMAND=C:amigentd
53
+ If WARN
54
+ Run >NIL: C:amigentd
55
+ EndIf
56
+ ; every installed program brings its own module
57
+ List $SRC/repair.d PAT=#?.repair LFORMAT "Execute %p%n" >T:amigent-modules
58
+ Execute T:amigent-modules
59
+ Delete >NIL: T:amigent-modules QUIET
60
+ Search >NIL: S:Startup-Sequence "S:User-Startup" QUIET
61
+ If WARN
62
+ RequestChoice >NIL: "Amigent" "Startup-Sequence no longer runs S:User-Startup.*NPlease tell the AI agent." "OK"
63
+ Else
64
+ RequestChoice >NIL: "Amigent" "Amigent is repaired.*NPlease restart the Amiga." "OK"
65
+ EndIf
@@ -0,0 +1,9 @@
1
+ ; amigent-scan: inventory of programs and their $VER strings.
2
+ ; Writes RAM:amigent-inventory.txt, then RAM:amigent-scan.done
3
+ FailAt 21
4
+ Delete >NIL: RAM:amigent-inventory.txt RAM:amigent-scan.script RAM:amigent-scan.done QUIET
5
+ Echo >RAM:amigent-scan.script "FailAt 21"
6
+ ChangeTaskPri -5
7
+ List >>RAM:amigent-scan.script SYS:C SYS:L SYS:Libs SYS:Classes SYS:Devs SYS:Expansion SYS:Internet SYS:Prefs SYS:Rexxc SYS:Storage SYS:System SYS:Tools SYS:Utilities SYS:WBStartup ALL FILES PAT=~(#?.(info|guide|txt|doc|readme|pdf|html|htm|png|jpg|jpeg|gif|iff|ilbm|mod|wav|mp3|h|c|i|s|asm|fd|cat|catalog|font|pfb|ttf|cfg|prefs|lha|lzx|zip)) LFORMAT "Echo >>RAM:amigent-inventory.txt *"@%p%n|%l*"*NVersion >>RAM:amigent-inventory.txt *"%p%n*""
8
+ Execute >NIL: RAM:amigent-scan.script
9
+ Echo >RAM:amigent-scan.done "done"
@@ -0,0 +1,45 @@
1
+ /* Inserts an entry into the AmiStart menu file, sorted by name.
2
+ Args: SECTION|Name|Program[|Stack]. AmiStart shows it after its next start.
3
+ The file is handled as one string because READLN splits lines over 1000 chars. */
4
+ parse arg section '|' name '|' file '|' stack
5
+ if stack = '' then stack = 32768
6
+ prefs = 'SYS:Utilities/Expansion/AmiStart/sm.prefs'
7
+ temp = 'T:sm.prefs.new'
8
+ nl = '0a'x
9
+ item = 'ITEM ICON="'file'" NAME="'name'" METHOD="0" CMDMODE="0" RGB="0x0" TEXTRGB="0x0" FILE="'file'" EXECMODE="0" STACK="'stack'"'
10
+
11
+ if ~open('in', prefs, 'R') then exit 10
12
+ data = readch('in', 65535)
13
+ call close('in')
14
+ if length(data) > 65000 then exit 10
15
+
16
+ start = index(data, nl'NEWDIR NAME="'section'"')
17
+ if start = 0 then exit 10
18
+ stop = index(data, nl'ENDDIR', start)
19
+ if stop = 0 then exit 10
20
+
21
+ insert = stop + 1
22
+ p = index(data, nl, start + 1) + 1
23
+ do while p < stop
24
+ e = index(data, nl, p)
25
+ line = substr(data, p, e - p)
26
+ if left(line, 5) == 'ITEM ' & index(line, '" GROUP ') = 0 then do
27
+ parse var line 'NAME="' current '"'
28
+ if upper(current) > upper(name) then do
29
+ insert = p
30
+ leave
31
+ end
32
+ end
33
+ p = e + 1
34
+ end
35
+
36
+ if ~open('out', temp, 'W') then exit 10
37
+ call writech('out', left(data, insert - 1) || item || nl || substr(data, insert))
38
+ call close('out')
39
+
40
+ do try = 1 to 10
41
+ address command 'Copy' temp prefs 'QUIET'
42
+ if rc = 0 then exit 0
43
+ address command 'Wait 1'
44
+ end
45
+ exit 10
@@ -0,0 +1,199 @@
1
+ """Client for the amigactld wire protocol: files, commands, system state. The old-school path.
2
+
3
+ Clean-room job from the published spec (docs/protocol.md and
4
+ docs/protocol-commands.md in the amigactl project), not a rip of its client code.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import socket
10
+
11
+ from .client import ExecResult, VolumeFlush, join_path
12
+ from .errors import AmigaError
13
+ from .roster import address
14
+
15
+ __all__ = ["AmigaCtl", "AmigaError", "ExecResult", "join_path"] # the old addresses still deliver
16
+
17
+ ENCODING = "iso-8859-1"
18
+ CHUNK_SIZE = 4096
19
+ MAX_LINE = 4096
20
+
21
+
22
+ class AmigaCtl(VolumeFlush):
23
+ def __init__(self, host: str, port: int = 6800, timeout: float = 30.0):
24
+ self.host = host
25
+ self.port = port
26
+ self.timeout = timeout
27
+ self._sock: socket.socket | None = None
28
+ self._buf = b""
29
+ self.banner = ""
30
+
31
+ def connect(self) -> None:
32
+ self.close()
33
+ try:
34
+ self._sock = socket.create_connection((address(self.host), self.port), self.timeout)
35
+ self.banner = self._line()
36
+ except OSError as e:
37
+ self.close()
38
+ raise AmigaError(f"cannot reach amigactld at {self.host}:{self.port}: {e}") from e
39
+ if not self.banner.startswith("AMIGACTL "):
40
+ banner, self.banner = self.banner, ""
41
+ self.close()
42
+ raise AmigaError(f"unexpected banner: {banner!r}")
43
+
44
+ def close(self) -> None:
45
+ if self._sock:
46
+ self._sock.close()
47
+ self._sock = None
48
+ self._buf = b""
49
+
50
+ # framing
51
+
52
+ def _recv(self) -> None:
53
+ assert self._sock
54
+ data = self._sock.recv(65536)
55
+ if not data:
56
+ raise AmigaError("connection closed by daemon")
57
+ self._buf += data
58
+
59
+ def _line(self) -> str:
60
+ while b"\n" not in self._buf:
61
+ self._recv()
62
+ raw, self._buf = self._buf.split(b"\n", 1)
63
+ return raw.rstrip(b"\r").decode(ENCODING)
64
+
65
+ def _exact(self, n: int) -> bytes:
66
+ while len(self._buf) < n:
67
+ self._recv()
68
+ data, self._buf = self._buf[:n], self._buf[n:]
69
+ return data
70
+
71
+ def _send_line(self, line: str) -> None:
72
+ raw = line.encode(ENCODING) + b"\n"
73
+ if len(raw) - 1 > MAX_LINE or "\n" in line:
74
+ raise AmigaError("command line too long or contains a newline")
75
+ if not self._sock:
76
+ self.connect()
77
+ assert self._sock
78
+ self._sock.sendall(raw)
79
+
80
+ def _status(self, line: str) -> str:
81
+ """The info field of an OK line; on ERR eat the sentinel and raise."""
82
+ if line == "OK" or line.startswith("OK "):
83
+ return line[3:]
84
+ if line.startswith("ERR "):
85
+ parts = line.split(" ", 2)
86
+ self._line() # sentinel
87
+ code = int(parts[1]) if parts[1].isdigit() else None
88
+ raise AmigaError(parts[2] if len(parts) > 2 else line, code)
89
+ raise AmigaError(f"malformed response: {line!r}")
90
+
91
+ def _text(self, command: str, *extra_lines: str) -> tuple[str, list[str]]:
92
+ """Run a command whose answer is dot-stuffed lines, SMTP style, ended by '.'."""
93
+ self._send_line(command)
94
+ for line in extra_lines:
95
+ self._send_line(line)
96
+ info = self._status(self._line())
97
+ lines = []
98
+ while True:
99
+ line = self._line()
100
+ if line == ".":
101
+ return info, lines
102
+ lines.append(line[1:] if line.startswith("..") else line)
103
+
104
+ def _binary(self, command: str, timeout: float | None = None) -> tuple[str, bytes]:
105
+ """Run a command whose answer comes in DATA chunks until END."""
106
+ self._send_line(command)
107
+ assert self._sock
108
+ self._sock.settimeout(timeout if timeout is not None else self.timeout)
109
+ try:
110
+ info = self._status(self._line())
111
+ chunks = []
112
+ while True:
113
+ line = self._line()
114
+ if line == "END":
115
+ break
116
+ if not line.startswith("DATA "):
117
+ raise AmigaError(f"malformed chunk header: {line!r}")
118
+ chunks.append(self._exact(int(line[5:])))
119
+ if self._line() != ".":
120
+ raise AmigaError("missing sentinel")
121
+ return info, b"".join(chunks)
122
+ finally:
123
+ if self._sock:
124
+ self._sock.settimeout(self.timeout)
125
+
126
+ # files
127
+
128
+ def list_dir(self, path: str, recursive: bool = False) -> list[dict]:
129
+ _, lines = self._text(f"DIR {path}" + (" RECURSIVE" if recursive else ""))
130
+ entries = []
131
+ for line in lines:
132
+ kind, name, size, protection, stamp = line.split("\t")
133
+ entries.append(
134
+ {"type": kind, "name": name, "size": int(size), "protection": protection, "date": stamp}
135
+ )
136
+ return entries
137
+
138
+ def stat(self, path: str) -> dict:
139
+ _, lines = self._text(f"STAT {path}")
140
+ return dict(line.split("=", 1) for line in lines)
141
+
142
+ def read_file(self, path: str) -> bytes:
143
+ return self._binary(f"READ {path}")[1]
144
+
145
+ def write_file(self, path: str, data: bytes, append: bool = False) -> None:
146
+ self._send_line(f"{'APPEND' if append else 'WRITE'} {path} {len(data)}")
147
+ line = self._line()
148
+ if line != "READY":
149
+ self._status(line)
150
+ raise AmigaError(f"expected READY, got {line!r}")
151
+ assert self._sock
152
+ for i in range(0, len(data), CHUNK_SIZE):
153
+ chunk = data[i : i + CHUNK_SIZE]
154
+ self._sock.sendall(f"DATA {len(chunk)}\n".encode() + chunk)
155
+ self._sock.sendall(b"END\n")
156
+ self._status(self._line())
157
+ if self._line() != ".":
158
+ raise AmigaError("missing sentinel")
159
+
160
+ def checksum(self, path: str, size: int = 0) -> int: # size: AmigaChannel uses it for patience
161
+ return int(dict(line.split("=", 1) for line in self._text(f"CHECKSUM {path}")[1])["crc32"], 16)
162
+
163
+ def delete(self, path: str) -> None:
164
+ self._text(f"DELETE {path}")
165
+
166
+ def rename(self, old: str, new: str) -> None:
167
+ self._text("RENAME", old, new)
168
+
169
+ def copy(self, source: str, dest: str) -> None:
170
+ self._text("COPY", source, dest)
171
+
172
+ def makedir(self, path: str) -> None:
173
+ self._text(f"MAKEDIR {path}")
174
+
175
+ # commands
176
+
177
+ def exec(self, command: str, cd: str | None = None, timeout: float | None = None) -> ExecResult:
178
+ """Run synchronously. The daemon is single-tasking here: everyone waits till it returns."""
179
+ prefix = f"CD={cd} " if cd else ""
180
+ info, data = self._binary(f"EXEC {prefix}{command}", timeout)
181
+ return ExecResult(int(info.split("=", 1)[1]), data.decode(ENCODING))
182
+
183
+ def exec_async(self, command: str) -> int:
184
+ """Fire and forget, no output captured; returns the daemon's process id."""
185
+ return int(self._text(f"EXEC ASYNC {command}")[0])
186
+
187
+ # system state
188
+
189
+ def sysinfo(self) -> dict:
190
+ _, lines = self._text("SYSINFO")
191
+ return dict(line.split("=", 1) for line in lines)
192
+
193
+ def volumes(self) -> list[dict]:
194
+ _, lines = self._text("VOLUMES")
195
+ keys = ("name", "used", "free", "capacity", "blocksize")
196
+ return [
197
+ {k: (v if k == "name" else int(v)) for k, v in zip(keys, line.split("\t"))}
198
+ for line in lines
199
+ ]