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.
- amigent-1.0.0/LICENSE +21 -0
- amigent-1.0.0/PKG-INFO +138 -0
- amigent-1.0.0/README.md +110 -0
- amigent-1.0.0/pyproject.toml +53 -0
- amigent-1.0.0/setup.cfg +4 -0
- amigent-1.0.0/src/amigent/__init__.py +3 -0
- amigent-1.0.0/src/amigent/amiga/amigent-repair +65 -0
- amigent-1.0.0/src/amigent/amiga/amigent-repair.info +0 -0
- amigent-1.0.0/src/amigent/amiga/amigent-scan +9 -0
- amigent-1.0.0/src/amigent/amiga/bin/Amigent.info +0 -0
- amigent-1.0.0/src/amigent/amiga/bin/amigentd +0 -0
- amigent-1.0.0/src/amigent/amiga/bin/amigentd.000 +0 -0
- amigent-1.0.0/src/amigent/amiga/bin/amigentd.info +0 -0
- amigent-1.0.0/src/amigent/amiga/menu-add.rexx +45 -0
- amigent-1.0.0/src/amigent/amigactl.py +199 -0
- amigent-1.0.0/src/amigent/amigentd.py +466 -0
- amigent-1.0.0/src/amigent/channel.py +411 -0
- amigent-1.0.0/src/amigent/client.py +26 -0
- amigent-1.0.0/src/amigent/desktop.py +308 -0
- amigent-1.0.0/src/amigent/doctor.py +70 -0
- amigent-1.0.0/src/amigent/errors.py +11 -0
- amigent-1.0.0/src/amigent/files.py +236 -0
- amigent-1.0.0/src/amigent/fleet.py +252 -0
- amigent-1.0.0/src/amigent/inventory.py +37 -0
- amigent-1.0.0/src/amigent/legacy.py +90 -0
- amigent-1.0.0/src/amigent/locks.py +44 -0
- amigent-1.0.0/src/amigent/lzh.py +175 -0
- amigent-1.0.0/src/amigent/machines.py +126 -0
- amigent-1.0.0/src/amigent/macos/Amigent.swift +76 -0
- amigent-1.0.0/src/amigent/macos/Amigent.zip +0 -0
- amigent-1.0.0/src/amigent/net.py +29 -0
- amigent-1.0.0/src/amigent/paths.py +18 -0
- amigent-1.0.0/src/amigent/pictures.py +104 -0
- amigent-1.0.0/src/amigent/platform_util.py +22 -0
- amigent-1.0.0/src/amigent/power.py +66 -0
- amigent-1.0.0/src/amigent/roster.py +163 -0
- amigent-1.0.0/src/amigent/secret.py +73 -0
- amigent-1.0.0/src/amigent/serve.py +84 -0
- amigent-1.0.0/src/amigent/server.py +723 -0
- amigent-1.0.0/src/amigent/setup.py +177 -0
- amigent-1.0.0/src/amigent/tls.py +150 -0
- amigent-1.0.0/src/amigent/transfer.py +64 -0
- amigent-1.0.0/src/amigent/viewer.py +911 -0
- amigent-1.0.0/src/amigent/watch.py +71 -0
- amigent-1.0.0/src/amigent/web/amigent.icns +0 -0
- amigent-1.0.0/src/amigent/web/amigent.ico +0 -0
- amigent-1.0.0/src/amigent/web/fleet.css +22 -0
- amigent-1.0.0/src/amigent/web/fleet.html +29 -0
- amigent-1.0.0/src/amigent/web/fleet.js +158 -0
- amigent-1.0.0/src/amigent/web/icon.png +0 -0
- amigent-1.0.0/src/amigent/web/icon.svg +15 -0
- amigent-1.0.0/src/amigent/web/index.html +246 -0
- amigent-1.0.0/src/amigent/web/locales/cs.json +129 -0
- amigent-1.0.0/src/amigent/web/locales/da.json +129 -0
- amigent-1.0.0/src/amigent/web/locales/de.json +129 -0
- amigent-1.0.0/src/amigent/web/locales/en.json +129 -0
- amigent-1.0.0/src/amigent/web/locales/es.json +129 -0
- amigent-1.0.0/src/amigent/web/locales/fi.json +129 -0
- amigent-1.0.0/src/amigent/web/locales/fr.json +129 -0
- amigent-1.0.0/src/amigent/web/locales/hu.json +129 -0
- amigent-1.0.0/src/amigent/web/locales/it.json +129 -0
- amigent-1.0.0/src/amigent/web/locales/pl.json +129 -0
- amigent-1.0.0/src/amigent/web/locales/sk.json +129 -0
- amigent-1.0.0/src/amigent/web/locales/sv.json +129 -0
- amigent-1.0.0/src/amigent/web/viewer.css +448 -0
- amigent-1.0.0/src/amigent/web/viewer.js +939 -0
- amigent-1.0.0/src/amigent.egg-info/PKG-INFO +138 -0
- amigent-1.0.0/src/amigent.egg-info/SOURCES.txt +84 -0
- amigent-1.0.0/src/amigent.egg-info/dependency_links.txt +1 -0
- amigent-1.0.0/src/amigent.egg-info/entry_points.txt +6 -0
- amigent-1.0.0/src/amigent.egg-info/requires.txt +2 -0
- amigent-1.0.0/src/amigent.egg-info/top_level.txt +1 -0
- amigent-1.0.0/tests/test_amigactl.py +119 -0
- amigent-1.0.0/tests/test_amigentd.py +79 -0
- amigent-1.0.0/tests/test_channel.py +217 -0
- amigent-1.0.0/tests/test_errors.py +24 -0
- amigent-1.0.0/tests/test_fast.py +218 -0
- amigent-1.0.0/tests/test_fleet.py +66 -0
- amigent-1.0.0/tests/test_inventory.py +26 -0
- amigent-1.0.0/tests/test_lzh.py +22 -0
- amigent-1.0.0/tests/test_machines.py +93 -0
- amigent-1.0.0/tests/test_net.py +41 -0
- amigent-1.0.0/tests/test_power.py +61 -0
- amigent-1.0.0/tests/test_protocol2.py +279 -0
- amigent-1.0.0/tests/test_secret.py +30 -0
- 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
|
+

|
|
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).
|
amigent-1.0.0/README.md
ADDED
|
@@ -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
|
+

|
|
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"]
|
amigent-1.0.0/setup.cfg
ADDED
|
@@ -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
|
|
Binary file
|
|
@@ -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"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
+
]
|