flyshell 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.
- flyshell-1.0.0/LICENSE +21 -0
- flyshell-1.0.0/PKG-INFO +208 -0
- flyshell-1.0.0/README.md +194 -0
- flyshell-1.0.0/pyproject.toml +28 -0
- flyshell-1.0.0/setup.cfg +4 -0
- flyshell-1.0.0/src/flyshell/__init__.py +7 -0
- flyshell-1.0.0/src/flyshell/abstract/__init__.py +0 -0
- flyshell-1.0.0/src/flyshell/abstract/base_cmd.py +26 -0
- flyshell-1.0.0/src/flyshell/abstract/base_plugin.py +75 -0
- flyshell-1.0.0/src/flyshell/abstract/parent_cmd.py +59 -0
- flyshell-1.0.0/src/flyshell/builtin/__init__.py +0 -0
- flyshell-1.0.0/src/flyshell/builtin/fs.py +161 -0
- flyshell-1.0.0/src/flyshell/builtin/system.py +124 -0
- flyshell-1.0.0/src/flyshell/builtin/utils.py +113 -0
- flyshell-1.0.0/src/flyshell/core/__init__.py +0 -0
- flyshell-1.0.0/src/flyshell/core/auth.py +122 -0
- flyshell-1.0.0/src/flyshell/core/console.py +64 -0
- flyshell-1.0.0/src/flyshell/core/data.py +234 -0
- flyshell-1.0.0/src/flyshell/core/directory.py +242 -0
- flyshell-1.0.0/src/flyshell/core/loader.py +90 -0
- flyshell-1.0.0/src/flyshell/main.py +115 -0
- flyshell-1.0.0/src/flyshell.egg-info/PKG-INFO +208 -0
- flyshell-1.0.0/src/flyshell.egg-info/SOURCES.txt +30 -0
- flyshell-1.0.0/src/flyshell.egg-info/dependency_links.txt +1 -0
- flyshell-1.0.0/src/flyshell.egg-info/entry_points.txt +2 -0
- flyshell-1.0.0/src/flyshell.egg-info/requires.txt +8 -0
- flyshell-1.0.0/src/flyshell.egg-info/top_level.txt +1 -0
- flyshell-1.0.0/tests/test_auth.py +26 -0
- flyshell-1.0.0/tests/test_data.py +63 -0
- flyshell-1.0.0/tests/test_directory.py +59 -0
- flyshell-1.0.0/tests/test_loader.py +39 -0
- flyshell-1.0.0/tests/test_security.py +33 -0
flyshell-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kianodev
|
|
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.
|
flyshell-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flyshell
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A modular, zero-dependency command-line environment in Python.
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest; extra == "dev"
|
|
11
|
+
Provides-Extra: readline
|
|
12
|
+
Requires-Dist: pyreadline3; sys_platform == "win32" and extra == "readline"
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# Flyshell
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+

|
|
19
|
+

|
|
20
|
+

|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
> Documentation last updated: v1.0.0
|
|
24
|
+
|
|
25
|
+
A modular, zero-dependency command-line environment and extensible runtime interface built in Python.
|
|
26
|
+
|
|
27
|
+
Flyshell provides a modular, extensible shell environment featuring dynamic runtime plugin loading, session persistence, secure authentication, and cross-platform process isolation, using only Python standard libraries.
|
|
28
|
+
|
|
29
|
+
No extra packages required, just install and enjoy!
|
|
30
|
+
|
|
31
|
+
## Key Features
|
|
32
|
+
|
|
33
|
+
1. **Zero External Dependencies** - Flyshell is built entirely with native Python modules with absolutely zero third-party packages required to enjoy full functionality.
|
|
34
|
+
2. **Dynamic Plugin Architecture**: Flyshell employs a dynamic plugin architecture that automatically scans for plugins in the User Directory following the standard defined in `src/flyshell/abstract/base_plugin.py`.
|
|
35
|
+
3. **Secure Authentication**: Flyshell uses salted PBKDF2 HMAC (SHA-256, 600,000 iterations) with constant-time equality comparisons to prevent timing attacks on password information.
|
|
36
|
+
4. **Cross-Platform Support**: Flyshell is fully supported by Windows (NT), macOS (Darwin) and Linux installations with native OS management and POSIX-aware parsing. Note that Flyshell is **not** supported by any other systems.
|
|
37
|
+
5. **State and History Persistence**: Flyshell has a fully encapsulated SQL storage system using SQLite3 providing session metrics and ISO 8601 UTC audit logging.
|
|
38
|
+
|
|
39
|
+
## System Requirements
|
|
40
|
+
|
|
41
|
+
1. **Python**: Your Python installation or IDE must support **Python 3.10 or newer** as Flyshell utilises structural pattern matching features. Older Python installations will be rejected by Flyshell on boot or could crash the system.
|
|
42
|
+
2. **Operating System**: Your operating system must be **Windows 10/11, macOS (Darwin) or Linux**. Any other operating system will be rejected by Flyshell on boot.
|
|
43
|
+
|
|
44
|
+
## System Architecture
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
flyshell/
|
|
48
|
+
├── .github/
|
|
49
|
+
│ └── workflows/
|
|
50
|
+
│ └── pytest.yml
|
|
51
|
+
├── src/
|
|
52
|
+
│ └── flyshell/
|
|
53
|
+
│ ├── abstract/
|
|
54
|
+
│ │ ├── __init__.py
|
|
55
|
+
│ │ ├── base_cmd.py
|
|
56
|
+
│ │ ├── base_plugin.py
|
|
57
|
+
│ │ └── parent_cmd.py
|
|
58
|
+
│ ├── builtin/
|
|
59
|
+
│ │ ├── __init__.py
|
|
60
|
+
│ │ ├── fs.py
|
|
61
|
+
│ │ ├── system.py
|
|
62
|
+
│ │ └── utils.py
|
|
63
|
+
│ ├── core/
|
|
64
|
+
│ │ ├── __init__.py
|
|
65
|
+
│ │ ├── auth.py
|
|
66
|
+
│ │ ├── console.py
|
|
67
|
+
│ │ ├── data.py
|
|
68
|
+
│ │ ├── directory.py
|
|
69
|
+
│ │ └── loader.py
|
|
70
|
+
│ ├── __init__.py
|
|
71
|
+
│ └── main.py
|
|
72
|
+
├── tests/
|
|
73
|
+
│ ├── test_auth.py
|
|
74
|
+
│ ├── test_data.py
|
|
75
|
+
│ ├── test_directory.py
|
|
76
|
+
│ ├── test_loader.py
|
|
77
|
+
│ └── test_security.py
|
|
78
|
+
├── .gitignore
|
|
79
|
+
├── LICENSE
|
|
80
|
+
├── pyproject.toml
|
|
81
|
+
└── README.md
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Installing Flyshell
|
|
85
|
+
|
|
86
|
+
There are two methods to install Flyshell.
|
|
87
|
+
|
|
88
|
+
### Method 1: Install via `pip` (Best for General Use)
|
|
89
|
+
|
|
90
|
+
Install Flyshell through `pip`:
|
|
91
|
+
```bash
|
|
92
|
+
pip install flyshell
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Then, you can simply run Flyshell:
|
|
96
|
+
```bash
|
|
97
|
+
flyshell
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Method 2: Install from source via `git` (Best for Development & Custom Plugins)
|
|
101
|
+
|
|
102
|
+
Clone the GitHub repository:
|
|
103
|
+
```bash
|
|
104
|
+
git clone https://github.com/kianodev/flyshell.git
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Switch to the Flyshell directory and install in editable mode:
|
|
108
|
+
```bash
|
|
109
|
+
cd flyshell
|
|
110
|
+
pip install -e .
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Adding optional dependencies
|
|
114
|
+
|
|
115
|
+
To install Readline (on Windows):
|
|
116
|
+
```bash
|
|
117
|
+
pip install -e .[readline]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
To install the development and testing suite (including Pytest):
|
|
121
|
+
```bash
|
|
122
|
+
pip install -e .[dev]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Exploring Flyshell
|
|
126
|
+
|
|
127
|
+
Once you have installed Flyshell using either of the methods above, you will be prompted to initialise your user profile. Once this is complete, you will be greeted with a prompt that looks like this:
|
|
128
|
+
```console
|
|
129
|
+
Flyshell [v1.x.x] (current_dir)>>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
From this stage, you can enter a wide variety of commands. To see the commands list for your installation, type `cmds` or `help` to return a full up-to-date directory. Use `fs plugins` to return plugin information.
|
|
133
|
+
|
|
134
|
+
Flyshell also supports command chaining out of the box. Use `;` to chain commands together regardless of result, `&&` to execute only on success and `||` to execute only on failure.
|
|
135
|
+
|
|
136
|
+
Use `-h` or `--help` after any command to return that command's specific help list.
|
|
137
|
+
|
|
138
|
+
To close Flyshell once you are done, enter `kill` onto the command line then confirm, or press Ctrl + D to close the session immediately.
|
|
139
|
+
|
|
140
|
+
## Creating Custom Plugins
|
|
141
|
+
|
|
142
|
+
Flyshell features an extensible plugin API. This API supports both single-file and multi-file plugins. Follow the below guide to create your own.
|
|
143
|
+
|
|
144
|
+
Plugins must inherit from the `BasePlugin` contract defined in `src/flyshell/abstract/base_plugin.py`. Plugins are stored in the User Directory. Navigate to your App Storage folder:
|
|
145
|
+
- **Windows**: `%APPDATA%\Flyshell\plugins`
|
|
146
|
+
- **macOS**: `~/Library/Application Support/Flyshell/plugins`
|
|
147
|
+
- **Linux**: `~/.local/share/Flyshell/plugins`
|
|
148
|
+
|
|
149
|
+
Place your plugins inside the `plugins/` folder.
|
|
150
|
+
|
|
151
|
+
All plugins should define the required attributes. Flyshell can read this information for both `fs plugins` and `[my_plugin] -h` / `--help` which are both supported out of the box. Flyshell will discover, inspect and import your plugin from the `plugins/` folder without you having to alter any internal code.
|
|
152
|
+
|
|
153
|
+
### The Entry-Point
|
|
154
|
+
|
|
155
|
+
All plugins must have an entry-point file. For single-file plugins, this is the plugin file. For multi-file plugins, this file must be named `plugin.py` and stored directly inside of your plugin's own folder.
|
|
156
|
+
|
|
157
|
+
This file is what enables Flyshell to speak to your plugin.
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
# Entry-point (my_plugin.py or my_plugin\plugin.py)
|
|
161
|
+
|
|
162
|
+
from flyshell import BasePlugin # This is required
|
|
163
|
+
|
|
164
|
+
class MyPlugin(BasePlugin):
|
|
165
|
+
name = "My Plugin" # The name you want visible to Flyshell
|
|
166
|
+
description = "This is my plugin." # A short one-line explaining what your plugin does
|
|
167
|
+
version = "1.0.0" # Default is 1.0.0
|
|
168
|
+
author = "kianodev" # You can put your name, your GitHub handle, or leave blank to make it 'Unknown'
|
|
169
|
+
usage = "" # (OPTIONAL) Explain how to use your plugin
|
|
170
|
+
extended_help = "" # (OPTIONAL) A more detailed, multi-line guide containing specific assistance
|
|
171
|
+
|
|
172
|
+
def on_load(self): # (OPTIONAL) Enables your program to handle specialist set-up tasks before execute()
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
def on_unload(self): # (OPTIONAL) Enables your program to handle specialist shut-down tasks
|
|
176
|
+
super().on_unload() # If you use a custom on_unload(), end it with this
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def execute(self, args): # This is what Flyshell calls to execute
|
|
180
|
+
print(f"Hello! Arguments: {args}")
|
|
181
|
+
return
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Extra Functions
|
|
185
|
+
|
|
186
|
+
Additional functions are available for you to use:
|
|
187
|
+
|
|
188
|
+
1. `get_data(key, default=None)`: Retrieves a value from local plugin memory.
|
|
189
|
+
2. `set_data(key, value)`: Updates a key in local memory and automatically persists it to SQLite via `save_storage()`.
|
|
190
|
+
3. `load_storage()`: Reloads fresh key-value pairs directly from SQLite into `self.storage`.
|
|
191
|
+
4. `save_storage()`: Writes all in-memory key-value pairs in `self.storage` back to SQLite.
|
|
192
|
+
|
|
193
|
+
Plugins save automatically on shut-down. If `on_unload()` is not used, it is automatic. If `on_unload()` is used, `super().on_unload()` saves your files.
|
|
194
|
+
|
|
195
|
+
### Built-in Attributes
|
|
196
|
+
|
|
197
|
+
Every plugin has direct access to the following instance variables:
|
|
198
|
+
|
|
199
|
+
- `self.plugin_name` (`str`): The unique name identifier used to invoke the plugin.
|
|
200
|
+
- `self.storage` (`dict`): In-memory key-value data automatically loaded from the persistent database.
|
|
201
|
+
- `self.context` (`dict`): Environmental metadata provided at start-up, within it:
|
|
202
|
+
- `self.context["metadata"]["host_os"]`: Operating system (e.g., `'Windows'`, `'Linux'`, `'Darwin'`).
|
|
203
|
+
- `self.context["metadata"]["version"]`: Current Flyshell version.
|
|
204
|
+
- `self.context["file_path"]`: Full path to the plugin's source file.
|
|
205
|
+
|
|
206
|
+
### Extra Notes
|
|
207
|
+
|
|
208
|
+
You are welcome to catch errors yourself. However, Flyshell will handle them automatically. That said, if you would like your plugin to continue after an error, you need to implement error handling yourself as the default system closes your plugin.
|
flyshell-1.0.0/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Flyshell
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
> Documentation last updated: v1.0.0
|
|
10
|
+
|
|
11
|
+
A modular, zero-dependency command-line environment and extensible runtime interface built in Python.
|
|
12
|
+
|
|
13
|
+
Flyshell provides a modular, extensible shell environment featuring dynamic runtime plugin loading, session persistence, secure authentication, and cross-platform process isolation, using only Python standard libraries.
|
|
14
|
+
|
|
15
|
+
No extra packages required, just install and enjoy!
|
|
16
|
+
|
|
17
|
+
## Key Features
|
|
18
|
+
|
|
19
|
+
1. **Zero External Dependencies** - Flyshell is built entirely with native Python modules with absolutely zero third-party packages required to enjoy full functionality.
|
|
20
|
+
2. **Dynamic Plugin Architecture**: Flyshell employs a dynamic plugin architecture that automatically scans for plugins in the User Directory following the standard defined in `src/flyshell/abstract/base_plugin.py`.
|
|
21
|
+
3. **Secure Authentication**: Flyshell uses salted PBKDF2 HMAC (SHA-256, 600,000 iterations) with constant-time equality comparisons to prevent timing attacks on password information.
|
|
22
|
+
4. **Cross-Platform Support**: Flyshell is fully supported by Windows (NT), macOS (Darwin) and Linux installations with native OS management and POSIX-aware parsing. Note that Flyshell is **not** supported by any other systems.
|
|
23
|
+
5. **State and History Persistence**: Flyshell has a fully encapsulated SQL storage system using SQLite3 providing session metrics and ISO 8601 UTC audit logging.
|
|
24
|
+
|
|
25
|
+
## System Requirements
|
|
26
|
+
|
|
27
|
+
1. **Python**: Your Python installation or IDE must support **Python 3.10 or newer** as Flyshell utilises structural pattern matching features. Older Python installations will be rejected by Flyshell on boot or could crash the system.
|
|
28
|
+
2. **Operating System**: Your operating system must be **Windows 10/11, macOS (Darwin) or Linux**. Any other operating system will be rejected by Flyshell on boot.
|
|
29
|
+
|
|
30
|
+
## System Architecture
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
flyshell/
|
|
34
|
+
├── .github/
|
|
35
|
+
│ └── workflows/
|
|
36
|
+
│ └── pytest.yml
|
|
37
|
+
├── src/
|
|
38
|
+
│ └── flyshell/
|
|
39
|
+
│ ├── abstract/
|
|
40
|
+
│ │ ├── __init__.py
|
|
41
|
+
│ │ ├── base_cmd.py
|
|
42
|
+
│ │ ├── base_plugin.py
|
|
43
|
+
│ │ └── parent_cmd.py
|
|
44
|
+
│ ├── builtin/
|
|
45
|
+
│ │ ├── __init__.py
|
|
46
|
+
│ │ ├── fs.py
|
|
47
|
+
│ │ ├── system.py
|
|
48
|
+
│ │ └── utils.py
|
|
49
|
+
│ ├── core/
|
|
50
|
+
│ │ ├── __init__.py
|
|
51
|
+
│ │ ├── auth.py
|
|
52
|
+
│ │ ├── console.py
|
|
53
|
+
│ │ ├── data.py
|
|
54
|
+
│ │ ├── directory.py
|
|
55
|
+
│ │ └── loader.py
|
|
56
|
+
│ ├── __init__.py
|
|
57
|
+
│ └── main.py
|
|
58
|
+
├── tests/
|
|
59
|
+
│ ├── test_auth.py
|
|
60
|
+
│ ├── test_data.py
|
|
61
|
+
│ ├── test_directory.py
|
|
62
|
+
│ ├── test_loader.py
|
|
63
|
+
│ └── test_security.py
|
|
64
|
+
├── .gitignore
|
|
65
|
+
├── LICENSE
|
|
66
|
+
├── pyproject.toml
|
|
67
|
+
└── README.md
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Installing Flyshell
|
|
71
|
+
|
|
72
|
+
There are two methods to install Flyshell.
|
|
73
|
+
|
|
74
|
+
### Method 1: Install via `pip` (Best for General Use)
|
|
75
|
+
|
|
76
|
+
Install Flyshell through `pip`:
|
|
77
|
+
```bash
|
|
78
|
+
pip install flyshell
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Then, you can simply run Flyshell:
|
|
82
|
+
```bash
|
|
83
|
+
flyshell
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Method 2: Install from source via `git` (Best for Development & Custom Plugins)
|
|
87
|
+
|
|
88
|
+
Clone the GitHub repository:
|
|
89
|
+
```bash
|
|
90
|
+
git clone https://github.com/kianodev/flyshell.git
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Switch to the Flyshell directory and install in editable mode:
|
|
94
|
+
```bash
|
|
95
|
+
cd flyshell
|
|
96
|
+
pip install -e .
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Adding optional dependencies
|
|
100
|
+
|
|
101
|
+
To install Readline (on Windows):
|
|
102
|
+
```bash
|
|
103
|
+
pip install -e .[readline]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
To install the development and testing suite (including Pytest):
|
|
107
|
+
```bash
|
|
108
|
+
pip install -e .[dev]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Exploring Flyshell
|
|
112
|
+
|
|
113
|
+
Once you have installed Flyshell using either of the methods above, you will be prompted to initialise your user profile. Once this is complete, you will be greeted with a prompt that looks like this:
|
|
114
|
+
```console
|
|
115
|
+
Flyshell [v1.x.x] (current_dir)>>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
From this stage, you can enter a wide variety of commands. To see the commands list for your installation, type `cmds` or `help` to return a full up-to-date directory. Use `fs plugins` to return plugin information.
|
|
119
|
+
|
|
120
|
+
Flyshell also supports command chaining out of the box. Use `;` to chain commands together regardless of result, `&&` to execute only on success and `||` to execute only on failure.
|
|
121
|
+
|
|
122
|
+
Use `-h` or `--help` after any command to return that command's specific help list.
|
|
123
|
+
|
|
124
|
+
To close Flyshell once you are done, enter `kill` onto the command line then confirm, or press Ctrl + D to close the session immediately.
|
|
125
|
+
|
|
126
|
+
## Creating Custom Plugins
|
|
127
|
+
|
|
128
|
+
Flyshell features an extensible plugin API. This API supports both single-file and multi-file plugins. Follow the below guide to create your own.
|
|
129
|
+
|
|
130
|
+
Plugins must inherit from the `BasePlugin` contract defined in `src/flyshell/abstract/base_plugin.py`. Plugins are stored in the User Directory. Navigate to your App Storage folder:
|
|
131
|
+
- **Windows**: `%APPDATA%\Flyshell\plugins`
|
|
132
|
+
- **macOS**: `~/Library/Application Support/Flyshell/plugins`
|
|
133
|
+
- **Linux**: `~/.local/share/Flyshell/plugins`
|
|
134
|
+
|
|
135
|
+
Place your plugins inside the `plugins/` folder.
|
|
136
|
+
|
|
137
|
+
All plugins should define the required attributes. Flyshell can read this information for both `fs plugins` and `[my_plugin] -h` / `--help` which are both supported out of the box. Flyshell will discover, inspect and import your plugin from the `plugins/` folder without you having to alter any internal code.
|
|
138
|
+
|
|
139
|
+
### The Entry-Point
|
|
140
|
+
|
|
141
|
+
All plugins must have an entry-point file. For single-file plugins, this is the plugin file. For multi-file plugins, this file must be named `plugin.py` and stored directly inside of your plugin's own folder.
|
|
142
|
+
|
|
143
|
+
This file is what enables Flyshell to speak to your plugin.
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
# Entry-point (my_plugin.py or my_plugin\plugin.py)
|
|
147
|
+
|
|
148
|
+
from flyshell import BasePlugin # This is required
|
|
149
|
+
|
|
150
|
+
class MyPlugin(BasePlugin):
|
|
151
|
+
name = "My Plugin" # The name you want visible to Flyshell
|
|
152
|
+
description = "This is my plugin." # A short one-line explaining what your plugin does
|
|
153
|
+
version = "1.0.0" # Default is 1.0.0
|
|
154
|
+
author = "kianodev" # You can put your name, your GitHub handle, or leave blank to make it 'Unknown'
|
|
155
|
+
usage = "" # (OPTIONAL) Explain how to use your plugin
|
|
156
|
+
extended_help = "" # (OPTIONAL) A more detailed, multi-line guide containing specific assistance
|
|
157
|
+
|
|
158
|
+
def on_load(self): # (OPTIONAL) Enables your program to handle specialist set-up tasks before execute()
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
def on_unload(self): # (OPTIONAL) Enables your program to handle specialist shut-down tasks
|
|
162
|
+
super().on_unload() # If you use a custom on_unload(), end it with this
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def execute(self, args): # This is what Flyshell calls to execute
|
|
166
|
+
print(f"Hello! Arguments: {args}")
|
|
167
|
+
return
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Extra Functions
|
|
171
|
+
|
|
172
|
+
Additional functions are available for you to use:
|
|
173
|
+
|
|
174
|
+
1. `get_data(key, default=None)`: Retrieves a value from local plugin memory.
|
|
175
|
+
2. `set_data(key, value)`: Updates a key in local memory and automatically persists it to SQLite via `save_storage()`.
|
|
176
|
+
3. `load_storage()`: Reloads fresh key-value pairs directly from SQLite into `self.storage`.
|
|
177
|
+
4. `save_storage()`: Writes all in-memory key-value pairs in `self.storage` back to SQLite.
|
|
178
|
+
|
|
179
|
+
Plugins save automatically on shut-down. If `on_unload()` is not used, it is automatic. If `on_unload()` is used, `super().on_unload()` saves your files.
|
|
180
|
+
|
|
181
|
+
### Built-in Attributes
|
|
182
|
+
|
|
183
|
+
Every plugin has direct access to the following instance variables:
|
|
184
|
+
|
|
185
|
+
- `self.plugin_name` (`str`): The unique name identifier used to invoke the plugin.
|
|
186
|
+
- `self.storage` (`dict`): In-memory key-value data automatically loaded from the persistent database.
|
|
187
|
+
- `self.context` (`dict`): Environmental metadata provided at start-up, within it:
|
|
188
|
+
- `self.context["metadata"]["host_os"]`: Operating system (e.g., `'Windows'`, `'Linux'`, `'Darwin'`).
|
|
189
|
+
- `self.context["metadata"]["version"]`: Current Flyshell version.
|
|
190
|
+
- `self.context["file_path"]`: Full path to the plugin's source file.
|
|
191
|
+
|
|
192
|
+
### Extra Notes
|
|
193
|
+
|
|
194
|
+
You are welcome to catch errors yourself. However, Flyshell will handle them automatically. That said, if you would like your plugin to continue after an error, you need to implement error handling yourself as the default system closes your plugin.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "flyshell"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "A modular, zero-dependency command-line environment in Python."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
flyshell = "flyshell.main:main"
|
|
15
|
+
|
|
16
|
+
[tool.setuptools.dynamic]
|
|
17
|
+
version = { attr = "flyshell.core.data.VERSION" }
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["src"]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
dev = [
|
|
24
|
+
"pytest",
|
|
25
|
+
]
|
|
26
|
+
readline = [
|
|
27
|
+
"pyreadline3; sys_platform == 'win32'",
|
|
28
|
+
]
|
flyshell-1.0.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# src\flyshell\abstract\base_cmd.py
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional, Tuple, Union
|
|
4
|
+
|
|
5
|
+
class BaseCmd:
|
|
6
|
+
name: str = ""
|
|
7
|
+
description: str = ""
|
|
8
|
+
min_args: int = 0
|
|
9
|
+
options: Optional[str] = None
|
|
10
|
+
aliases: List[str] = []
|
|
11
|
+
|
|
12
|
+
def execute(self, args: List[str]) -> Union[int, Tuple[int, str], str, None]:
|
|
13
|
+
raise NotImplementedError(f"Command '{self.__class__.__name__}' has not implemented the execute method")
|
|
14
|
+
|
|
15
|
+
def help(self, prefix: str = ""):
|
|
16
|
+
full_name = f"{prefix} {self.name}".strip() if prefix else self.name
|
|
17
|
+
print(f"\nCommand: {full_name}")
|
|
18
|
+
if prefix:
|
|
19
|
+
print(f"Parent: Part of the '{prefix}' family of commands")
|
|
20
|
+
if self.aliases:
|
|
21
|
+
print(f"Aliases: {', '.join(self.aliases)}")
|
|
22
|
+
print(f"Description: {self.description}")
|
|
23
|
+
print(f"Required Arguments: {self.min_args}")
|
|
24
|
+
if self.options:
|
|
25
|
+
print(f"Accepted Arguments: {self.options}")
|
|
26
|
+
print()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# src\flyshell\abstract\base_plugin.py
|
|
2
|
+
|
|
3
|
+
from flyshell.core import data
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
class BasePlugin:
|
|
7
|
+
name: str = "Unnamed Plugin"
|
|
8
|
+
description: str = "No description provided."
|
|
9
|
+
version: str = "1.0.0"
|
|
10
|
+
author: str = "Unknown"
|
|
11
|
+
usage: str = ""
|
|
12
|
+
extended_help: str = ""
|
|
13
|
+
|
|
14
|
+
def __init__(self, context: dict):
|
|
15
|
+
self.context = context
|
|
16
|
+
self.plugin_name = context.get("plugin_name", self.name)
|
|
17
|
+
self.storage = context.get("storage", {})
|
|
18
|
+
self.on_load()
|
|
19
|
+
|
|
20
|
+
def on_load(self):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def on_unload(self):
|
|
24
|
+
self.save_storage()
|
|
25
|
+
|
|
26
|
+
def execute(self, args: list):
|
|
27
|
+
raise NotImplementedError("Plugins must implement the execute method")
|
|
28
|
+
|
|
29
|
+
def help(self):
|
|
30
|
+
lines = [
|
|
31
|
+
f"\nPlugin: {self.name} (v{self.version})",
|
|
32
|
+
f"Description: {self.description}",
|
|
33
|
+
f"Author: {self.author}"
|
|
34
|
+
]
|
|
35
|
+
if self.usage:
|
|
36
|
+
lines.append(f"Usage: {self.usage}")
|
|
37
|
+
if self.extended_help:
|
|
38
|
+
lines.append("\nDetails:")
|
|
39
|
+
lines.append(self.extended_help)
|
|
40
|
+
print("\n".join(lines))
|
|
41
|
+
|
|
42
|
+
def show_docs(self) -> bool:
|
|
43
|
+
plugin_path = self.context.get("file_path")
|
|
44
|
+
if plugin_path:
|
|
45
|
+
p = Path(plugin_path)
|
|
46
|
+
doc_file = (p.parent / "README.md") if p.is_file() else (p / "README.md")
|
|
47
|
+
if doc_file.exists():
|
|
48
|
+
print(f"\nDocumentation: {self.name}")
|
|
49
|
+
print(doc_file.read_text(encoding="utf-8"))
|
|
50
|
+
print("-" * 40)
|
|
51
|
+
return True
|
|
52
|
+
if self.__doc__:
|
|
53
|
+
print(f"\nDocumentation: {self.name}")
|
|
54
|
+
print(self.__doc__.strip())
|
|
55
|
+
print("-" * 40)
|
|
56
|
+
return True
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
def get_data(self, key: str, default=None):
|
|
60
|
+
return self.storage.get(key, default)
|
|
61
|
+
|
|
62
|
+
def set_data(self, key: str, value):
|
|
63
|
+
self.storage[key] = value
|
|
64
|
+
self.save_storage()
|
|
65
|
+
|
|
66
|
+
def load_storage(self) -> dict:
|
|
67
|
+
fresh_data = data.get_all_plugin_data(self.plugin_name)
|
|
68
|
+
self.storage = fresh_data
|
|
69
|
+
self.context["storage"] = fresh_data
|
|
70
|
+
return self.storage
|
|
71
|
+
|
|
72
|
+
def save_storage(self):
|
|
73
|
+
for k, v in self.storage.items():
|
|
74
|
+
data.set_plugin_data(self.plugin_name, k, v)
|
|
75
|
+
self.context["storage"] = self.storage
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# src\flyshell\abstract\parent_cmd.py
|
|
2
|
+
|
|
3
|
+
from flyshell.abstract.base_cmd import BaseCmd
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List, Optional, Tuple, Union
|
|
6
|
+
|
|
7
|
+
class ParentCmd(BaseCmd):
|
|
8
|
+
min_args: int = 1
|
|
9
|
+
subcommands: Dict[str, BaseCmd] = {}
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
super().__init__() if hasattr(super(), "__init__") else None
|
|
13
|
+
self._sub_registry: Dict[str, BaseCmd] = {}
|
|
14
|
+
self._build_registry()
|
|
15
|
+
|
|
16
|
+
def _build_registry(self):
|
|
17
|
+
for cmd_obj in self.get_subcommands():
|
|
18
|
+
self._sub_registry[cmd_obj.name] = cmd_obj
|
|
19
|
+
for alias in getattr(cmd_obj, "aliases", []):
|
|
20
|
+
self._sub_registry[alias] = cmd_obj
|
|
21
|
+
|
|
22
|
+
def get_subcommands(self) -> List[BaseCmd]:
|
|
23
|
+
return []
|
|
24
|
+
|
|
25
|
+
def help(self):
|
|
26
|
+
print(f"\nCommand Family: {self.name}")
|
|
27
|
+
print(f"Description: {self.description}")
|
|
28
|
+
print(f"\nSubcommands of '{self.name}':")
|
|
29
|
+
seen = set()
|
|
30
|
+
for sub in self._sub_registry.values():
|
|
31
|
+
if sub in seen:
|
|
32
|
+
continue
|
|
33
|
+
seen.add(sub)
|
|
34
|
+
label = f"{sub.name} (alias: {', '.join(sub.aliases)})" if sub.aliases else sub.name
|
|
35
|
+
opt = f" [args: {sub.options}]" if sub.options else ""
|
|
36
|
+
print(f"{label} - {sub.description}{opt}")
|
|
37
|
+
print(f"\nFor subcommand assistance use '{self.name} <subcmd> -h' or '--help'\n")
|
|
38
|
+
|
|
39
|
+
def execute(self, args: List[str]) -> Union[int, Tuple[int, str], str, None]:
|
|
40
|
+
if not args or args[0] in ("-h", "--help"):
|
|
41
|
+
self.help()
|
|
42
|
+
return 0
|
|
43
|
+
sub_name = args[0].lower()
|
|
44
|
+
sub_args = args[1:]
|
|
45
|
+
if sub_name not in self._sub_registry:
|
|
46
|
+
print(f"\nCommand Error: Unknown subcommand '{sub_name}' for '{self.name}'.")
|
|
47
|
+
print(f"Use '{self.name} -h' or '--help' to see all valid subcommands.\n")
|
|
48
|
+
return 1
|
|
49
|
+
target = self._sub_registry[sub_name]
|
|
50
|
+
if "-h" in sub_args or "--help" in sub_args:
|
|
51
|
+
target.help(prefix=self.name)
|
|
52
|
+
return 0
|
|
53
|
+
if len(sub_args) < target.min_args:
|
|
54
|
+
print(f"\nCommand Error: '{self.name} {target.name}' requires at least {target.min_args} argument(s).")
|
|
55
|
+
if target.options:
|
|
56
|
+
print(f"Accepted arguments: {target.options}")
|
|
57
|
+
print()
|
|
58
|
+
return 1
|
|
59
|
+
return target.execute(sub_args)
|
|
File without changes
|