flyshell 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flyshell/__init__.py +7 -0
- flyshell/abstract/__init__.py +0 -0
- flyshell/abstract/base_cmd.py +26 -0
- flyshell/abstract/base_plugin.py +75 -0
- flyshell/abstract/parent_cmd.py +59 -0
- flyshell/builtin/__init__.py +0 -0
- flyshell/builtin/fs.py +161 -0
- flyshell/builtin/system.py +124 -0
- flyshell/builtin/utils.py +113 -0
- flyshell/core/__init__.py +0 -0
- flyshell/core/auth.py +122 -0
- flyshell/core/console.py +64 -0
- flyshell/core/data.py +234 -0
- flyshell/core/directory.py +242 -0
- flyshell/core/loader.py +90 -0
- flyshell/main.py +115 -0
- flyshell-1.0.0.dist-info/METADATA +208 -0
- flyshell-1.0.0.dist-info/RECORD +22 -0
- flyshell-1.0.0.dist-info/WHEEL +5 -0
- flyshell-1.0.0.dist-info/entry_points.txt +2 -0
- flyshell-1.0.0.dist-info/licenses/LICENSE +21 -0
- flyshell-1.0.0.dist-info/top_level.txt +1 -0
flyshell/__init__.py
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
|
flyshell/builtin/fs.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# src\flyshell\builtin\fs.py
|
|
2
|
+
|
|
3
|
+
from flyshell.abstract.base_cmd import BaseCmd
|
|
4
|
+
from flyshell.abstract.parent_cmd import ParentCmd
|
|
5
|
+
from flyshell.core import data
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
class FsDel(BaseCmd):
|
|
12
|
+
name = "del"
|
|
13
|
+
description = "Delete / purge an area of Flyshell system storage"
|
|
14
|
+
min_args = 1
|
|
15
|
+
options = "'history', 'all', 'plugin <file>'"
|
|
16
|
+
aliases = ["purge"]
|
|
17
|
+
def execute(self, args):
|
|
18
|
+
if len(args) < 1:
|
|
19
|
+
return (1, "\nCommand Error: 'fs del' requires a target.\n")
|
|
20
|
+
target = args[0].lower()
|
|
21
|
+
match target:
|
|
22
|
+
case "history":
|
|
23
|
+
choice = input("\nAre you sure you want to clear all command history? [y/n]: ").strip().lower()
|
|
24
|
+
if choice == "y":
|
|
25
|
+
data.clear_history()
|
|
26
|
+
return (0, "\nCommand history purged successfully.\n")
|
|
27
|
+
return (1, "\nAction cancelled.\n")
|
|
28
|
+
case "config":
|
|
29
|
+
if len(args) < 2:
|
|
30
|
+
return (1, "\nCommand Error: Please specify a configuration key to delete.\n")
|
|
31
|
+
key = args[1]
|
|
32
|
+
if data.delete_config(key):
|
|
33
|
+
return (0, f"\nConfig setting '{key}' deleted.\n")
|
|
34
|
+
return (1, f"\nConfig Error: Setting '{key}' not found.\n")
|
|
35
|
+
case "plugin":
|
|
36
|
+
if len(args) < 2:
|
|
37
|
+
return (1, "\nCommand Error: Please specify a plugin name to purge.\n")
|
|
38
|
+
plugin_name = args[1]
|
|
39
|
+
choice = input(f"\nAre you sure you want to purge all data for plugin '{plugin_name}'? [y/n]: ").strip().lower()
|
|
40
|
+
if choice == "y":
|
|
41
|
+
if data.purge_plugin(plugin_name):
|
|
42
|
+
return (0, f"\nPlugin '{plugin_name}' and its storage purged successfully.\n")
|
|
43
|
+
return (1, f"\nPlugin Error: No data found for plugin '{plugin_name}'.\n")
|
|
44
|
+
return (1, "\nAction cancelled.\n")
|
|
45
|
+
case "all":
|
|
46
|
+
choice = input("\n⚠️ WARNING: This will wipe history, configurations, and plugin data. Proceed? [y/n]: ").strip().lower()
|
|
47
|
+
if choice == "y":
|
|
48
|
+
data.clear_history()
|
|
49
|
+
for name in list(data.PLUGINS.keys()):
|
|
50
|
+
data.purge_plugin(name)
|
|
51
|
+
return (0, "\nAll non-auth system data purged successfully.\n")
|
|
52
|
+
return (1, "\nAction cancelled.\n")
|
|
53
|
+
case _:
|
|
54
|
+
return (1, f"\nCommand Error: Unknown target '{target}'.\n")
|
|
55
|
+
|
|
56
|
+
class FsInfo(BaseCmd):
|
|
57
|
+
name = "info"
|
|
58
|
+
description = "Display Flyshell system information"
|
|
59
|
+
min_args = 0
|
|
60
|
+
options = None
|
|
61
|
+
aliases = []
|
|
62
|
+
|
|
63
|
+
def execute(self, args):
|
|
64
|
+
return (0, (
|
|
65
|
+
"\nFlyshell System Information:\n"
|
|
66
|
+
"System Name: Flyshell\n"
|
|
67
|
+
f"System Version: {data.VERSION}\n"
|
|
68
|
+
f"System Build: {data.BUILD}\n"
|
|
69
|
+
f"Host Operating System: '{data.HOST_OS}'\n"
|
|
70
|
+
f"Installed Plugin Count: {len(data.PLUGINS)}\n"
|
|
71
|
+
"Original Release Date: 8th August 2026\n"
|
|
72
|
+
))
|
|
73
|
+
|
|
74
|
+
class FsLicence(BaseCmd):
|
|
75
|
+
name = "licence"
|
|
76
|
+
description = "Display Flyshell licence conditions"
|
|
77
|
+
min_args = 0
|
|
78
|
+
options = None
|
|
79
|
+
aliases = ["license"]
|
|
80
|
+
|
|
81
|
+
def execute(self, args):
|
|
82
|
+
return (0, (
|
|
83
|
+
"\nFlyshell Licensing Information:\n"
|
|
84
|
+
"Flyshell is licensed under the MIT Licence\n\n"
|
|
85
|
+
"Copyright (c) 2026 kianodev\n\n"
|
|
86
|
+
"Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files [the 'Software'], to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\n"
|
|
87
|
+
"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\n"
|
|
88
|
+
"THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
|
|
89
|
+
))
|
|
90
|
+
|
|
91
|
+
class FsPlugins(BaseCmd):
|
|
92
|
+
name = "plugins"
|
|
93
|
+
description = "Display installed plugins and their details"
|
|
94
|
+
min_args = 0
|
|
95
|
+
options = None
|
|
96
|
+
aliases = ["plugin"]
|
|
97
|
+
|
|
98
|
+
def execute(self, args):
|
|
99
|
+
lines = ["\nInstalled Plugins:"]
|
|
100
|
+
if data.PLUGINS:
|
|
101
|
+
for name, plugin in data.PLUGINS.items():
|
|
102
|
+
p_name = getattr(plugin, "name", name)
|
|
103
|
+
p_desc = getattr(plugin, "description", "No description provided.")
|
|
104
|
+
lines.append(f"Plugin '{name}' [{p_name}]: {p_desc}")
|
|
105
|
+
else:
|
|
106
|
+
lines.append("No plugins installed.")
|
|
107
|
+
lines.append(f"\nTotal available plugins: {len(data.PLUGINS)}\n")
|
|
108
|
+
return (0, "\n".join(lines))
|
|
109
|
+
|
|
110
|
+
class FsStatus(BaseCmd):
|
|
111
|
+
name = "status"
|
|
112
|
+
description = "Return Flyshell status and runtime details"
|
|
113
|
+
min_args = 0
|
|
114
|
+
options = None
|
|
115
|
+
aliases = []
|
|
116
|
+
|
|
117
|
+
def execute(self, args):
|
|
118
|
+
uptime = int(time.time() - data.SESSION_START_TIME)
|
|
119
|
+
mins, secs = divmod(uptime, 60)
|
|
120
|
+
hours, mins = divmod(mins, 60)
|
|
121
|
+
uptime_str = f"{hours}h {mins}m {secs}s"
|
|
122
|
+
auth_data = data.get_auth() or {}
|
|
123
|
+
current_user = auth_data.get("username", "Unknown")
|
|
124
|
+
cmd_history = data.get_history()
|
|
125
|
+
total_history_count = len(cmd_history) if isinstance(cmd_history, list) else 0
|
|
126
|
+
storage_size_str = data.get_storage_size()
|
|
127
|
+
return (0, (
|
|
128
|
+
"\nFlyshell System Status:\n"
|
|
129
|
+
f"User: '{current_user}'\n"
|
|
130
|
+
f"Session Uptime: {uptime_str}\n"
|
|
131
|
+
f"Commands (Session): {data.SESSION_CMD_COUNT}\n"
|
|
132
|
+
f"Commands (Lifetime): {total_history_count}\n"
|
|
133
|
+
f"Working Directory: '{os.getcwd()}'\n"
|
|
134
|
+
f"Storage File Size: {storage_size_str}\n"
|
|
135
|
+
f"Python Environment: v{sys.version.split()[0]}\n"
|
|
136
|
+
f"\nFlyshell Version {data.VERSION} (Build {data.BUILD})\n"
|
|
137
|
+
))
|
|
138
|
+
|
|
139
|
+
class FsVersion(BaseCmd):
|
|
140
|
+
name = "version"
|
|
141
|
+
description = "Return Flyshell version and build"
|
|
142
|
+
min_args = 0
|
|
143
|
+
options = None
|
|
144
|
+
aliases = []
|
|
145
|
+
def execute(self, args):
|
|
146
|
+
return (0, f"\nYou are on Flyshell Version {data.VERSION} (Build {data.BUILD})\n")
|
|
147
|
+
|
|
148
|
+
class Fs(ParentCmd):
|
|
149
|
+
name = "fs"
|
|
150
|
+
description = "Execute core Flyshell control and utility functions"
|
|
151
|
+
aliases = []
|
|
152
|
+
|
|
153
|
+
def get_subcommands(self):
|
|
154
|
+
return [
|
|
155
|
+
FsDel(),
|
|
156
|
+
FsInfo(),
|
|
157
|
+
FsLicence(),
|
|
158
|
+
FsPlugins(),
|
|
159
|
+
FsStatus(),
|
|
160
|
+
FsVersion(),
|
|
161
|
+
]
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# src\flyshell\builtin\system.py
|
|
2
|
+
|
|
3
|
+
from flyshell.abstract.base_cmd import BaseCmd
|
|
4
|
+
from flyshell.core import auth, data
|
|
5
|
+
|
|
6
|
+
import getpass
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
class Cd(BaseCmd):
|
|
14
|
+
name = "cd"
|
|
15
|
+
description = "Change the current working directory"
|
|
16
|
+
min_args = 0
|
|
17
|
+
options = "Directory name (default is Home)"
|
|
18
|
+
aliases = []
|
|
19
|
+
|
|
20
|
+
def execute(self, args):
|
|
21
|
+
target = os.path.expanduser(" ".join(args)) if args else os.path.expanduser("~")
|
|
22
|
+
try:
|
|
23
|
+
os.chdir(target)
|
|
24
|
+
return (0, f"\nSwitched directory to '{os.getcwd()}'\n")
|
|
25
|
+
except FileNotFoundError:
|
|
26
|
+
return (1, f"\nCommand Error: Directory '{target}' not found.\n")
|
|
27
|
+
except PermissionError:
|
|
28
|
+
return (1, f"\nSystem Error: Permission denied. Cannot access '{target}'\n")
|
|
29
|
+
except OSError as e:
|
|
30
|
+
return (1, f"\nCommand Error: Invalid path syntax '{target}'. ({e.strerror})\n")
|
|
31
|
+
|
|
32
|
+
class Dir(BaseCmd):
|
|
33
|
+
name = "dir"
|
|
34
|
+
description = "List all files in the current working directory"
|
|
35
|
+
min_args = 0
|
|
36
|
+
options = None
|
|
37
|
+
aliases = ["ls"]
|
|
38
|
+
|
|
39
|
+
def execute(self, args):
|
|
40
|
+
try:
|
|
41
|
+
current_path = os.getcwd()
|
|
42
|
+
items = os.listdir(current_path)
|
|
43
|
+
lines = [f"\nDirectory: '{current_path}'"]
|
|
44
|
+
for item in items:
|
|
45
|
+
if os.path.isdir(os.path.join(current_path, item)):
|
|
46
|
+
lines.append(f"[DIR] {item}/")
|
|
47
|
+
else:
|
|
48
|
+
lines.append(f"[FILE] {item}")
|
|
49
|
+
lines.append(f"\nTotal items in directory: {len(items)}\n")
|
|
50
|
+
return (0, "\n".join(lines))
|
|
51
|
+
except Exception as e:
|
|
52
|
+
return (1, f"\nSystem Error: Unable to list directory ({e})\n")
|
|
53
|
+
|
|
54
|
+
class OpenFile(BaseCmd):
|
|
55
|
+
name = "open"
|
|
56
|
+
description = "Open the specified file path"
|
|
57
|
+
min_args = 1
|
|
58
|
+
options = "File path"
|
|
59
|
+
aliases = []
|
|
60
|
+
|
|
61
|
+
def execute(self, args):
|
|
62
|
+
target = " ".join(args).strip().strip('"\'')
|
|
63
|
+
if not target:
|
|
64
|
+
return (0, "")
|
|
65
|
+
is_path = os.path.exists(target)
|
|
66
|
+
is_url = target.startswith(("http://", "https://"))
|
|
67
|
+
is_bin = shutil.which(target) is not None
|
|
68
|
+
if not (is_path or is_url or is_bin):
|
|
69
|
+
return (1, f"\nCommand Error: File or application '{target}' could not be found.\n")
|
|
70
|
+
try:
|
|
71
|
+
if data.HOST_OS == "Windows":
|
|
72
|
+
os.startfile(target)
|
|
73
|
+
elif data.HOST_OS == "Darwin":
|
|
74
|
+
subprocess.Popen(["open", target], stderr=subprocess.DEVNULL)
|
|
75
|
+
else:
|
|
76
|
+
subprocess.Popen(["xdg-open", target], stderr=subprocess.DEVNULL)
|
|
77
|
+
return (0, f"\nSUCCESS! Launched '{target}' application or file.\n")
|
|
78
|
+
except Exception as e:
|
|
79
|
+
return (1, f"\nSystem Error: Failed to open '{target}': {e}\n")
|
|
80
|
+
|
|
81
|
+
class SysExe(BaseCmd):
|
|
82
|
+
name = "sys"
|
|
83
|
+
description = "Execute the subsequent command on the host system"
|
|
84
|
+
min_args = 1
|
|
85
|
+
options = "Any host command"
|
|
86
|
+
aliases = []
|
|
87
|
+
|
|
88
|
+
def execute(self, args):
|
|
89
|
+
if args[0] in ["-f", "--force"]:
|
|
90
|
+
override = True
|
|
91
|
+
args = args[1:]
|
|
92
|
+
if not args:
|
|
93
|
+
return (1, "\nCommand Error: 'sys' requires at least 1 parameter(s).\n")
|
|
94
|
+
else:
|
|
95
|
+
override = False
|
|
96
|
+
cmd = " ".join(args).strip().strip('"\'')
|
|
97
|
+
cmd_normalised = " ".join(cmd.lower().split())
|
|
98
|
+
BLOCKED_COMMANDS = {"format", "diskpart", "dd"}
|
|
99
|
+
is_blocked = any(re.search(rf"\b{re.escape(b)}\b", cmd_normalised) for b in BLOCKED_COMMANDS)
|
|
100
|
+
DANGER_COMMANDS = {"del", "rmdir", "rd", "rm", "taskkill", "killall"}
|
|
101
|
+
is_dangerous = any(re.search(rf"\b{re.escape(d)}\b", cmd_normalised) for d in DANGER_COMMANDS)
|
|
102
|
+
if is_blocked:
|
|
103
|
+
return (1, (
|
|
104
|
+
"\nSecurity Error: Flyshell has blocked execution of this command.\n"
|
|
105
|
+
f"This command '{cmd}' is permanently restricted by Flyshell safety policy.\n"
|
|
106
|
+
"Flyshell has blocked your command to prevent damage to your computer.\n"
|
|
107
|
+
))
|
|
108
|
+
elif is_dangerous and not override:
|
|
109
|
+
password = getpass.getpass("\nThis command is restricted. To execute, enter your password: ")
|
|
110
|
+
auth_info = data.get_auth() or {}
|
|
111
|
+
stored_hash = auth_info.get("hash")
|
|
112
|
+
stored_salt = auth_info.get("salt")
|
|
113
|
+
if not auth.verify_password(stored_hash, stored_salt, password):
|
|
114
|
+
return (1, "\nAccount Error: Password does not match.\n")
|
|
115
|
+
try:
|
|
116
|
+
print()
|
|
117
|
+
result = subprocess.run(cmd, shell=True)
|
|
118
|
+
print()
|
|
119
|
+
return (result.returncode, "")
|
|
120
|
+
except KeyboardInterrupt:
|
|
121
|
+
print()
|
|
122
|
+
return (130, "")
|
|
123
|
+
except Exception as e:
|
|
124
|
+
return (1, f"\nCommand Error: Failed to run host command ({e})\n")
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# src\flyshell\builtin\utils.py
|
|
2
|
+
|
|
3
|
+
from flyshell.abstract.base_cmd import BaseCmd
|
|
4
|
+
from flyshell.core import data
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import signal
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
class Clear(BaseCmd):
|
|
13
|
+
name = "clear"
|
|
14
|
+
description = "Clear the screen"
|
|
15
|
+
min_args = 0
|
|
16
|
+
options = None
|
|
17
|
+
aliases = ["cls"]
|
|
18
|
+
|
|
19
|
+
def execute(self, args):
|
|
20
|
+
return (0, "\033[H\033[2J")
|
|
21
|
+
|
|
22
|
+
class History(BaseCmd):
|
|
23
|
+
name = "history"
|
|
24
|
+
description = "View command history"
|
|
25
|
+
min_args = 0
|
|
26
|
+
options = "Entry count (default is 10)"
|
|
27
|
+
aliases = []
|
|
28
|
+
|
|
29
|
+
def execute(self, args):
|
|
30
|
+
limit = 10
|
|
31
|
+
if args:
|
|
32
|
+
sub_arg = args[0].lower()
|
|
33
|
+
if sub_arg == "all":
|
|
34
|
+
limit = None
|
|
35
|
+
elif sub_arg.isdigit():
|
|
36
|
+
limit = int(sub_arg)
|
|
37
|
+
else:
|
|
38
|
+
return (1, f"\nCommand Error: Invalid argument '{args[0]}'\n")
|
|
39
|
+
history_entries = data.get_history(limit)
|
|
40
|
+
if not history_entries:
|
|
41
|
+
return (0, "\nHistory is empty.\n")
|
|
42
|
+
lines = [
|
|
43
|
+
"\nCommand History:",
|
|
44
|
+
f"Showing last {len(history_entries)} entries.\n"
|
|
45
|
+
]
|
|
46
|
+
for i, entry in enumerate(history_entries, start=1):
|
|
47
|
+
cmd = entry.get("command", "")
|
|
48
|
+
time_str = entry.get("timestamp", "")
|
|
49
|
+
date_part, time_part = time_str.rstrip("Z").split("T")
|
|
50
|
+
lines.append(f"#{i}: {cmd} [at {date_part} @ {time_part} UTC]")
|
|
51
|
+
lines.append("")
|
|
52
|
+
return (0, "\n".join(lines))
|
|
53
|
+
|
|
54
|
+
class Kill(BaseCmd):
|
|
55
|
+
name = "kill"
|
|
56
|
+
description = "Shut down the Flyshell system"
|
|
57
|
+
min_args = 0
|
|
58
|
+
options = None
|
|
59
|
+
aliases = []
|
|
60
|
+
|
|
61
|
+
def execute(self, args):
|
|
62
|
+
choice = input("\nAre you sure [y/n]?: ").strip().lower()
|
|
63
|
+
if choice == "y":
|
|
64
|
+
print("\nShutting down...")
|
|
65
|
+
sys.exit(0)
|
|
66
|
+
else:
|
|
67
|
+
return (1, "\nAction cancelled.\n")
|
|
68
|
+
|
|
69
|
+
class Reboot(BaseCmd):
|
|
70
|
+
name = "reboot"
|
|
71
|
+
description = "Reboot the Flyshell system"
|
|
72
|
+
min_args = 0
|
|
73
|
+
options = None
|
|
74
|
+
aliases = ["restart"]
|
|
75
|
+
|
|
76
|
+
def execute(self, args):
|
|
77
|
+
print("\nRestarting Flyshell...\n")
|
|
78
|
+
is_direct_script = (
|
|
79
|
+
sys.argv[0].lower().endswith(".exe")
|
|
80
|
+
or os.path.basename(sys.argv[0]) == "flyshell"
|
|
81
|
+
)
|
|
82
|
+
if is_direct_script:
|
|
83
|
+
cmd = sys.argv
|
|
84
|
+
else:
|
|
85
|
+
cmd = [sys.executable] + sys.argv
|
|
86
|
+
if data.HOST_OS == "Windows":
|
|
87
|
+
old_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
88
|
+
try:
|
|
89
|
+
p = subprocess.run(cmd)
|
|
90
|
+
sys.exit(p.returncode)
|
|
91
|
+
finally:
|
|
92
|
+
signal.signal(signal.SIGINT, old_handler)
|
|
93
|
+
else:
|
|
94
|
+
os.execv(cmd[0], cmd)
|
|
95
|
+
|
|
96
|
+
class Sleep(BaseCmd):
|
|
97
|
+
name = "sleep"
|
|
98
|
+
description = "Sleep the system for a specified time"
|
|
99
|
+
min_args = 1
|
|
100
|
+
options = "Duration (secs)"
|
|
101
|
+
aliases = []
|
|
102
|
+
|
|
103
|
+
def execute(self, args):
|
|
104
|
+
if not args or not args[0].replace(".", "", 1).isdigit():
|
|
105
|
+
return (1, f"\nCommand Error: Invalid argument '{args[0] if args else ''}'\n")
|
|
106
|
+
secs = float(args[0])
|
|
107
|
+
try:
|
|
108
|
+
print(f"\nPausing Flyshell for {secs} second(s).")
|
|
109
|
+
print("Press Ctrl+C to cancel.\n")
|
|
110
|
+
time.sleep(secs)
|
|
111
|
+
return (0, "\nReturning...\n")
|
|
112
|
+
except KeyboardInterrupt:
|
|
113
|
+
return (1, "\nSleep cancelled.\n")
|
|
File without changes
|