flyshell 1.0.1__tar.gz → 1.0.3__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.1/src/flyshell.egg-info → flyshell-1.0.3}/PKG-INFO +4 -1
- {flyshell-1.0.1 → flyshell-1.0.3}/pyproject.toml +3 -0
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/__init__.py +2 -1
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/abstract/base_cmd.py +5 -2
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/abstract/base_plugin.py +3 -2
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/abstract/parent_cmd.py +17 -6
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/builtin/fs.py +84 -36
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/builtin/system.py +33 -13
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/builtin/utils.py +12 -4
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/constants.py +15 -13
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/core/auth.py +10 -10
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/core/console.py +6 -1
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/core/data.py +71 -19
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/core/directory.py +41 -14
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/core/loader.py +14 -12
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/main.py +37 -15
- {flyshell-1.0.1 → flyshell-1.0.3/src/flyshell.egg-info}/PKG-INFO +4 -1
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell.egg-info/requires.txt +3 -0
- {flyshell-1.0.1 → flyshell-1.0.3}/tests/test_auth.py +5 -1
- {flyshell-1.0.1 → flyshell-1.0.3}/tests/test_data.py +16 -3
- {flyshell-1.0.1 → flyshell-1.0.3}/tests/test_directory.py +17 -1
- {flyshell-1.0.1 → flyshell-1.0.3}/tests/test_loader.py +4 -1
- {flyshell-1.0.1 → flyshell-1.0.3}/tests/test_security.py +1 -1
- {flyshell-1.0.1 → flyshell-1.0.3}/LICENSE +0 -0
- {flyshell-1.0.1 → flyshell-1.0.3}/README.md +0 -0
- {flyshell-1.0.1 → flyshell-1.0.3}/setup.cfg +0 -0
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/abstract/__init__.py +0 -0
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/builtin/__init__.py +0 -0
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell/core/__init__.py +0 -0
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell.egg-info/SOURCES.txt +0 -0
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell.egg-info/dependency_links.txt +0 -0
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell.egg-info/entry_points.txt +0 -0
- {flyshell-1.0.1 → flyshell-1.0.3}/src/flyshell.egg-info/top_level.txt +0 -0
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flyshell
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: A modular, zero-dependency command-line environment in Python.
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: black; extra == "dev"
|
|
11
|
+
Requires-Dist: flake8; extra == "dev"
|
|
10
12
|
Requires-Dist: pytest; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
11
14
|
Provides-Extra: readline
|
|
12
15
|
Requires-Dist: pyreadline3; sys_platform == "win32" and extra == "readline"
|
|
13
16
|
Dynamic: license-file
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import List, Optional, Tuple, Union
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
class BaseCmd:
|
|
6
7
|
name: str = ""
|
|
7
8
|
description: str = ""
|
|
@@ -10,7 +11,9 @@ class BaseCmd:
|
|
|
10
11
|
aliases: List[str] = []
|
|
11
12
|
|
|
12
13
|
def execute(self, args: List[str]) -> Union[int, Tuple[int, str], str, None]:
|
|
13
|
-
raise NotImplementedError(
|
|
14
|
+
raise NotImplementedError(
|
|
15
|
+
f"Command '{self.__class__.__name__}' has not implemented the execute method"
|
|
16
|
+
)
|
|
14
17
|
|
|
15
18
|
def help(self, prefix: str = ""):
|
|
16
19
|
full_name = f"{prefix} {self.name}".strip() if prefix else self.name
|
|
@@ -23,4 +26,4 @@ class BaseCmd:
|
|
|
23
26
|
print(f"Required Arguments: {self.min_args}")
|
|
24
27
|
if self.options:
|
|
25
28
|
print(f"Accepted Arguments: {self.options}")
|
|
26
|
-
print()
|
|
29
|
+
print()
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from flyshell.core import data
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
class BasePlugin:
|
|
7
8
|
name: str = "Unnamed Plugin"
|
|
8
9
|
description: str = "No description provided."
|
|
@@ -30,7 +31,7 @@ class BasePlugin:
|
|
|
30
31
|
lines = [
|
|
31
32
|
f"\nPlugin: {self.name} (v{self.version})",
|
|
32
33
|
f"Description: {self.description}",
|
|
33
|
-
f"Author: {self.author}"
|
|
34
|
+
f"Author: {self.author}",
|
|
34
35
|
]
|
|
35
36
|
if self.usage:
|
|
36
37
|
lines.append(f"Usage: {self.usage}")
|
|
@@ -72,4 +73,4 @@ class BasePlugin:
|
|
|
72
73
|
def save_storage(self):
|
|
73
74
|
for k, v in self.storage.items():
|
|
74
75
|
data.set_plugin_data(self.plugin_name, k, v)
|
|
75
|
-
self.context["storage"] = self.storage
|
|
76
|
+
self.context["storage"] = self.storage
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from flyshell.abstract.base_cmd import BaseCmd
|
|
4
4
|
|
|
5
|
-
from typing import Dict, List,
|
|
5
|
+
from typing import Dict, List, Tuple, Union
|
|
6
|
+
|
|
6
7
|
|
|
7
8
|
class ParentCmd(BaseCmd):
|
|
8
9
|
min_args: int = 1
|
|
@@ -31,10 +32,16 @@ class ParentCmd(BaseCmd):
|
|
|
31
32
|
if sub in seen:
|
|
32
33
|
continue
|
|
33
34
|
seen.add(sub)
|
|
34
|
-
label =
|
|
35
|
+
label = (
|
|
36
|
+
f"{sub.name} (alias: {', '.join(sub.aliases)})"
|
|
37
|
+
if sub.aliases
|
|
38
|
+
else sub.name
|
|
39
|
+
)
|
|
35
40
|
opt = f" [args: {sub.options}]" if sub.options else ""
|
|
36
41
|
print(f"{label} - {sub.description}{opt}")
|
|
37
|
-
print(
|
|
42
|
+
print(
|
|
43
|
+
f"\nFor subcommand assistance use '{self.name} <subcmd> -h' or '--help'\n"
|
|
44
|
+
)
|
|
38
45
|
|
|
39
46
|
def execute(self, args: List[str]) -> Union[int, Tuple[int, str], str, None]:
|
|
40
47
|
if not args or args[0] in ("-h", "--help"):
|
|
@@ -43,7 +50,9 @@ class ParentCmd(BaseCmd):
|
|
|
43
50
|
sub_name = args[0].lower()
|
|
44
51
|
sub_args = args[1:]
|
|
45
52
|
if sub_name not in self._sub_registry:
|
|
46
|
-
print(
|
|
53
|
+
print(
|
|
54
|
+
f"\nCommand Error: Unknown subcommand '{sub_name}' for '{self.name}'."
|
|
55
|
+
)
|
|
47
56
|
print(f"Use '{self.name} -h' or '--help' to see all valid subcommands.\n")
|
|
48
57
|
return 1
|
|
49
58
|
target = self._sub_registry[sub_name]
|
|
@@ -51,9 +60,11 @@ class ParentCmd(BaseCmd):
|
|
|
51
60
|
target.help(prefix=self.name)
|
|
52
61
|
return 0
|
|
53
62
|
if len(sub_args) < target.min_args:
|
|
54
|
-
print(
|
|
63
|
+
print(
|
|
64
|
+
f"\nCommand Error: '{self.name} {target.name}' requires at least {target.min_args} argument(s)."
|
|
65
|
+
)
|
|
55
66
|
if target.options:
|
|
56
67
|
print(f"Accepted arguments: {target.options}")
|
|
57
68
|
print()
|
|
58
69
|
return 1
|
|
59
|
-
return target.execute(sub_args)
|
|
70
|
+
return target.execute(sub_args)
|
|
@@ -9,42 +9,74 @@ import os
|
|
|
9
9
|
import sys
|
|
10
10
|
import time
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
class FsDel(BaseCmd):
|
|
13
14
|
name = "del"
|
|
14
15
|
description = "Delete / purge an area of Flyshell system storage"
|
|
15
16
|
min_args = 1
|
|
16
17
|
options = "'history', 'all', 'plugin <file>'"
|
|
17
18
|
aliases = ["purge"]
|
|
19
|
+
|
|
18
20
|
def execute(self, args):
|
|
19
21
|
if len(args) < 1:
|
|
20
22
|
return (1, "\nCommand Error: 'fs del' requires a target.\n")
|
|
21
23
|
target = args[0].lower()
|
|
22
24
|
match target:
|
|
23
25
|
case "history":
|
|
24
|
-
choice =
|
|
26
|
+
choice = (
|
|
27
|
+
input(
|
|
28
|
+
"\nAre you sure you want to clear all command history? [y/n]: "
|
|
29
|
+
)
|
|
30
|
+
.strip()
|
|
31
|
+
.lower()
|
|
32
|
+
)
|
|
25
33
|
if choice == "y":
|
|
26
34
|
data.clear_history()
|
|
27
35
|
return (0, "\nCommand history purged successfully.\n")
|
|
28
36
|
return (1, "\nAction cancelled.\n")
|
|
29
37
|
case "config":
|
|
30
38
|
if len(args) < 2:
|
|
31
|
-
return (
|
|
39
|
+
return (
|
|
40
|
+
1,
|
|
41
|
+
"\nCommand Error: Please specify a configuration key to delete.\n",
|
|
42
|
+
)
|
|
32
43
|
key = args[1]
|
|
33
44
|
if data.delete_config(key):
|
|
34
45
|
return (0, f"\nConfig setting '{key}' deleted.\n")
|
|
35
46
|
return (1, f"\nConfig Error: Setting '{key}' not found.\n")
|
|
36
47
|
case "plugin":
|
|
37
48
|
if len(args) < 2:
|
|
38
|
-
return (
|
|
49
|
+
return (
|
|
50
|
+
1,
|
|
51
|
+
"\nCommand Error: Please specify a plugin name to purge.\n",
|
|
52
|
+
)
|
|
39
53
|
plugin_name = args[1]
|
|
40
|
-
choice =
|
|
54
|
+
choice = (
|
|
55
|
+
input(
|
|
56
|
+
f"\nAre you sure you want to purge all data for plugin '{plugin_name}'? [y/n]: "
|
|
57
|
+
)
|
|
58
|
+
.strip()
|
|
59
|
+
.lower()
|
|
60
|
+
)
|
|
41
61
|
if choice == "y":
|
|
42
62
|
if data.purge_plugin(plugin_name):
|
|
43
|
-
return (
|
|
44
|
-
|
|
63
|
+
return (
|
|
64
|
+
0,
|
|
65
|
+
f"\nPlugin '{plugin_name}' and its storage purged successfully.\n",
|
|
66
|
+
)
|
|
67
|
+
return (
|
|
68
|
+
1,
|
|
69
|
+
f"\nPlugin Error: No data found for plugin '{plugin_name}'.\n",
|
|
70
|
+
)
|
|
45
71
|
return (1, "\nAction cancelled.\n")
|
|
46
72
|
case "all":
|
|
47
|
-
choice =
|
|
73
|
+
choice = (
|
|
74
|
+
input(
|
|
75
|
+
"\n⚠️ WARNING: This will wipe history, configurations, and plugin data. Proceed? [y/n]: "
|
|
76
|
+
)
|
|
77
|
+
.strip()
|
|
78
|
+
.lower()
|
|
79
|
+
)
|
|
48
80
|
if choice == "y":
|
|
49
81
|
data.clear_history()
|
|
50
82
|
for name in list(data.PLUGINS.keys()):
|
|
@@ -54,6 +86,7 @@ class FsDel(BaseCmd):
|
|
|
54
86
|
case _:
|
|
55
87
|
return (1, f"\nCommand Error: Unknown target '{target}'.\n")
|
|
56
88
|
|
|
89
|
+
|
|
57
90
|
class FsInfo(BaseCmd):
|
|
58
91
|
name = "info"
|
|
59
92
|
description = "Display Flyshell system information"
|
|
@@ -62,15 +95,19 @@ class FsInfo(BaseCmd):
|
|
|
62
95
|
aliases = []
|
|
63
96
|
|
|
64
97
|
def execute(self, args):
|
|
65
|
-
return (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
98
|
+
return (
|
|
99
|
+
0,
|
|
100
|
+
(
|
|
101
|
+
"\nFlyshell System Information:\n"
|
|
102
|
+
"System Name: Flyshell\n"
|
|
103
|
+
f"System Version: {c.VERSION}\n"
|
|
104
|
+
f"System Build: {c.BUILD}\n"
|
|
105
|
+
f"Host Operating System: '{c.HOST_OS}'\n"
|
|
106
|
+
f"Installed Plugin Count: {len(c.PLUGINS)}\n"
|
|
107
|
+
"Original Release Date: 8th August 2026\n"
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
|
|
74
111
|
|
|
75
112
|
class FsLicence(BaseCmd):
|
|
76
113
|
name = "licence"
|
|
@@ -80,14 +117,18 @@ class FsLicence(BaseCmd):
|
|
|
80
117
|
aliases = ["license"]
|
|
81
118
|
|
|
82
119
|
def execute(self, args):
|
|
83
|
-
return (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
120
|
+
return (
|
|
121
|
+
0,
|
|
122
|
+
(
|
|
123
|
+
"\nFlyshell Licensing Information:\n"
|
|
124
|
+
"Flyshell is licensed under the MIT Licence\n\n"
|
|
125
|
+
"Copyright (c) 2026 kianodev\n\n"
|
|
126
|
+
"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"
|
|
127
|
+
"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\n"
|
|
128
|
+
"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"
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
|
|
91
132
|
|
|
92
133
|
class FsPlugins(BaseCmd):
|
|
93
134
|
name = "plugins"
|
|
@@ -108,6 +149,7 @@ class FsPlugins(BaseCmd):
|
|
|
108
149
|
lines.append(f"\nTotal available plugins: {len(c.PLUGINS)}\n")
|
|
109
150
|
return (0, "\n".join(lines))
|
|
110
151
|
|
|
152
|
+
|
|
111
153
|
class FsStatus(BaseCmd):
|
|
112
154
|
name = "status"
|
|
113
155
|
description = "Return Flyshell status and runtime details"
|
|
@@ -125,17 +167,21 @@ class FsStatus(BaseCmd):
|
|
|
125
167
|
cmd_history = data.get_history()
|
|
126
168
|
total_history_count = len(cmd_history) if isinstance(cmd_history, list) else 0
|
|
127
169
|
storage_size_str = data.get_storage_size()
|
|
128
|
-
return (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
170
|
+
return (
|
|
171
|
+
0,
|
|
172
|
+
(
|
|
173
|
+
"\nFlyshell System Status:\n"
|
|
174
|
+
f"User: '{current_user}'\n"
|
|
175
|
+
f"Session Uptime: {uptime_str}\n"
|
|
176
|
+
f"Commands (Session): {c.SESSION_CMD_COUNT}\n"
|
|
177
|
+
f"Commands (Lifetime): {total_history_count}\n"
|
|
178
|
+
f"Working Directory: '{os.getcwd()}'\n"
|
|
179
|
+
f"Storage File Size: {storage_size_str}\n"
|
|
180
|
+
f"Python Environment: v{sys.version.split()[0]}\n"
|
|
181
|
+
f"\nFlyshell Version {c.VERSION} (Build {c.BUILD})\n"
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
|
|
139
185
|
|
|
140
186
|
class FsVersion(BaseCmd):
|
|
141
187
|
name = "version"
|
|
@@ -143,9 +189,11 @@ class FsVersion(BaseCmd):
|
|
|
143
189
|
min_args = 0
|
|
144
190
|
options = None
|
|
145
191
|
aliases = []
|
|
192
|
+
|
|
146
193
|
def execute(self, args):
|
|
147
194
|
return (0, f"\nYou are on Flyshell Version {c.VERSION} (Build {c.BUILD})\n")
|
|
148
195
|
|
|
196
|
+
|
|
149
197
|
class Fs(ParentCmd):
|
|
150
198
|
name = "fs"
|
|
151
199
|
description = "Execute core Flyshell control and utility functions"
|
|
@@ -159,4 +207,4 @@ class Fs(ParentCmd):
|
|
|
159
207
|
FsPlugins(),
|
|
160
208
|
FsStatus(),
|
|
161
209
|
FsVersion(),
|
|
162
|
-
]
|
|
210
|
+
]
|
|
@@ -10,6 +10,7 @@ import re
|
|
|
10
10
|
import shutil
|
|
11
11
|
import subprocess
|
|
12
12
|
|
|
13
|
+
|
|
13
14
|
class Cd(BaseCmd):
|
|
14
15
|
name = "cd"
|
|
15
16
|
description = "Change the current working directory"
|
|
@@ -27,7 +28,11 @@ class Cd(BaseCmd):
|
|
|
27
28
|
except PermissionError:
|
|
28
29
|
return (1, f"\nSystem Error: Permission denied. Cannot access '{target}'\n")
|
|
29
30
|
except OSError as e:
|
|
30
|
-
return (
|
|
31
|
+
return (
|
|
32
|
+
1,
|
|
33
|
+
f"\nCommand Error: Invalid path syntax '{target}'. ({e.strerror})\n",
|
|
34
|
+
)
|
|
35
|
+
|
|
31
36
|
|
|
32
37
|
class Dir(BaseCmd):
|
|
33
38
|
name = "dir"
|
|
@@ -51,6 +56,7 @@ class Dir(BaseCmd):
|
|
|
51
56
|
except Exception as e:
|
|
52
57
|
return (1, f"\nSystem Error: Unable to list directory ({e})\n")
|
|
53
58
|
|
|
59
|
+
|
|
54
60
|
class OpenFile(BaseCmd):
|
|
55
61
|
name = "open"
|
|
56
62
|
description = "Open the specified file path"
|
|
@@ -59,14 +65,17 @@ class OpenFile(BaseCmd):
|
|
|
59
65
|
aliases = []
|
|
60
66
|
|
|
61
67
|
def execute(self, args):
|
|
62
|
-
target = " ".join(args).strip().strip(
|
|
68
|
+
target = " ".join(args).strip().strip("\"'")
|
|
63
69
|
if not target:
|
|
64
70
|
return (0, "")
|
|
65
71
|
is_path = os.path.exists(target)
|
|
66
72
|
is_url = target.startswith(("http://", "https://"))
|
|
67
73
|
is_bin = shutil.which(target) is not None
|
|
68
74
|
if not (is_path or is_url or is_bin):
|
|
69
|
-
return (
|
|
75
|
+
return (
|
|
76
|
+
1,
|
|
77
|
+
f"\nCommand Error: File or application '{target}' could not be found.\n",
|
|
78
|
+
)
|
|
70
79
|
try:
|
|
71
80
|
if c.HOST_OS == "Windows":
|
|
72
81
|
os.startfile(target)
|
|
@@ -78,6 +87,7 @@ class OpenFile(BaseCmd):
|
|
|
78
87
|
except Exception as e:
|
|
79
88
|
return (1, f"\nSystem Error: Failed to open '{target}': {e}\n")
|
|
80
89
|
|
|
90
|
+
|
|
81
91
|
class SysExe(BaseCmd):
|
|
82
92
|
name = "sys"
|
|
83
93
|
description = "Execute the subsequent command on the host system"
|
|
@@ -93,18 +103,28 @@ class SysExe(BaseCmd):
|
|
|
93
103
|
return (1, "\nCommand Error: 'sys' requires at least 1 parameter(s).\n")
|
|
94
104
|
else:
|
|
95
105
|
override = False
|
|
96
|
-
cmd = " ".join(args).strip().strip(
|
|
106
|
+
cmd = " ".join(args).strip().strip("\"'")
|
|
97
107
|
cmd_normalised = " ".join(cmd.lower().split())
|
|
98
|
-
is_blocked = any(
|
|
99
|
-
|
|
108
|
+
is_blocked = any(
|
|
109
|
+
re.search(rf"\b{re.escape(b)}\b", cmd_normalised)
|
|
110
|
+
for b in c.BLOCKED_COMMANDS
|
|
111
|
+
)
|
|
112
|
+
is_dangerous = any(
|
|
113
|
+
re.search(rf"\b{re.escape(d)}\b", cmd_normalised) for d in c.DANGER_COMMANDS
|
|
114
|
+
)
|
|
100
115
|
if is_blocked:
|
|
101
|
-
return (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
116
|
+
return (
|
|
117
|
+
1,
|
|
118
|
+
(
|
|
119
|
+
"\nSecurity Error: Flyshell has blocked execution of this command.\n"
|
|
120
|
+
f"This command '{cmd}' is permanently restricted by Flyshell safety policy.\n"
|
|
121
|
+
"Flyshell has blocked your command to prevent damage to your computer.\n"
|
|
122
|
+
),
|
|
123
|
+
)
|
|
106
124
|
elif is_dangerous and not override:
|
|
107
|
-
password = getpass.getpass(
|
|
125
|
+
password = getpass.getpass(
|
|
126
|
+
"\nThis command is restricted. To execute, enter your password: "
|
|
127
|
+
)
|
|
108
128
|
auth_info = data.get_auth() or {}
|
|
109
129
|
stored_hash = auth_info.get("hash")
|
|
110
130
|
stored_salt = auth_info.get("salt")
|
|
@@ -119,4 +139,4 @@ class SysExe(BaseCmd):
|
|
|
119
139
|
print()
|
|
120
140
|
return (130, "")
|
|
121
141
|
except Exception as e:
|
|
122
|
-
return (1, f"\nCommand Error: Failed to run host command ({e})\n")
|
|
142
|
+
return (1, f"\nCommand Error: Failed to run host command ({e})\n")
|
|
@@ -10,6 +10,7 @@ import subprocess
|
|
|
10
10
|
import sys
|
|
11
11
|
import time
|
|
12
12
|
|
|
13
|
+
|
|
13
14
|
class Clear(BaseCmd):
|
|
14
15
|
name = "clear"
|
|
15
16
|
description = "Clear the screen"
|
|
@@ -20,6 +21,7 @@ class Clear(BaseCmd):
|
|
|
20
21
|
def execute(self, args):
|
|
21
22
|
return (0, c.CLEAR_SCREEN)
|
|
22
23
|
|
|
24
|
+
|
|
23
25
|
class History(BaseCmd):
|
|
24
26
|
name = "history"
|
|
25
27
|
description = "View command history"
|
|
@@ -42,7 +44,7 @@ class History(BaseCmd):
|
|
|
42
44
|
return (0, "\nHistory is empty.\n")
|
|
43
45
|
lines = [
|
|
44
46
|
"\nCommand History:",
|
|
45
|
-
f"Showing last {len(history_entries)} entries.\n"
|
|
47
|
+
f"Showing last {len(history_entries)} entries.\n",
|
|
46
48
|
]
|
|
47
49
|
for i, entry in enumerate(history_entries, start=1):
|
|
48
50
|
cmd = entry.get("command", "")
|
|
@@ -52,6 +54,7 @@ class History(BaseCmd):
|
|
|
52
54
|
lines.append("")
|
|
53
55
|
return (0, "\n".join(lines))
|
|
54
56
|
|
|
57
|
+
|
|
55
58
|
class Kill(BaseCmd):
|
|
56
59
|
name = "kill"
|
|
57
60
|
description = "Shut down the Flyshell system"
|
|
@@ -67,6 +70,7 @@ class Kill(BaseCmd):
|
|
|
67
70
|
else:
|
|
68
71
|
return (1, "\nAction cancelled.\n")
|
|
69
72
|
|
|
73
|
+
|
|
70
74
|
class Reboot(BaseCmd):
|
|
71
75
|
name = "reboot"
|
|
72
76
|
description = "Reboot the Flyshell system"
|
|
@@ -77,7 +81,7 @@ class Reboot(BaseCmd):
|
|
|
77
81
|
def execute(self, args):
|
|
78
82
|
print("\nRestarting Flyshell...\n")
|
|
79
83
|
is_direct_script = (
|
|
80
|
-
sys.argv[0].lower().endswith(".exe")
|
|
84
|
+
sys.argv[0].lower().endswith(".exe")
|
|
81
85
|
or os.path.basename(sys.argv[0]) == "flyshell"
|
|
82
86
|
)
|
|
83
87
|
if is_direct_script:
|
|
@@ -94,6 +98,7 @@ class Reboot(BaseCmd):
|
|
|
94
98
|
else:
|
|
95
99
|
os.execv(cmd[0], cmd)
|
|
96
100
|
|
|
101
|
+
|
|
97
102
|
class Sleep(BaseCmd):
|
|
98
103
|
name = "sleep"
|
|
99
104
|
description = "Sleep the system for a specified time"
|
|
@@ -103,7 +108,10 @@ class Sleep(BaseCmd):
|
|
|
103
108
|
|
|
104
109
|
def execute(self, args):
|
|
105
110
|
if not args or not args[0].replace(".", "", 1).isdigit():
|
|
106
|
-
return (
|
|
111
|
+
return (
|
|
112
|
+
1,
|
|
113
|
+
f"\nCommand Error: Invalid argument '{args[0] if args else ''}'\n",
|
|
114
|
+
)
|
|
107
115
|
secs = float(args[0])
|
|
108
116
|
try:
|
|
109
117
|
print(f"\nPausing Flyshell for {secs} second(s).")
|
|
@@ -111,4 +119,4 @@ class Sleep(BaseCmd):
|
|
|
111
119
|
time.sleep(secs)
|
|
112
120
|
return (0, "\nReturning...\n")
|
|
113
121
|
except KeyboardInterrupt:
|
|
114
|
-
return (1, "\nSleep cancelled.\n")
|
|
122
|
+
return (1, "\nSleep cancelled.\n")
|
|
@@ -7,8 +7,9 @@ import os
|
|
|
7
7
|
import platform
|
|
8
8
|
import time
|
|
9
9
|
|
|
10
|
-
BUILD =
|
|
11
|
-
VERSION = "1.0.
|
|
10
|
+
BUILD = 102
|
|
11
|
+
VERSION = "1.0.3"
|
|
12
|
+
|
|
12
13
|
|
|
13
14
|
def _resolve_app_dir() -> Path:
|
|
14
15
|
home = Path.home()
|
|
@@ -22,6 +23,7 @@ def _resolve_app_dir() -> Path:
|
|
|
22
23
|
target.mkdir(parents=True, exist_ok=True)
|
|
23
24
|
return target
|
|
24
25
|
|
|
26
|
+
|
|
25
27
|
HOST_OS = platform.system()
|
|
26
28
|
PACKAGE_ROOT = Path(__file__).resolve().parent
|
|
27
29
|
SRC_DIR = PACKAGE_ROOT.parent
|
|
@@ -64,14 +66,14 @@ REQUIRED_COMPONENTS = [
|
|
|
64
66
|
]
|
|
65
67
|
|
|
66
68
|
GREETINGS = [
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
"Hi {username}, what's it going to be today?",
|
|
70
|
+
"The mic is yours, {username}, go and blow me away!",
|
|
71
|
+
"Hey {username}, let's get this party started!",
|
|
72
|
+
"What's up, {username}? Let's go!",
|
|
73
|
+
"I'm awaiting your first command, {username}!",
|
|
74
|
+
"How's it going {username}? I'm here to help.",
|
|
75
|
+
"{username}, it's your call! Show me what you've got, what are we doing today?",
|
|
76
|
+
"My wish is your command, {username}! (unless it's not on my command list)",
|
|
77
|
+
"I have goods for you {username}, it's called a Command Interface.",
|
|
78
|
+
"I'm your boss! Wait, no... sorry... you, {username}, are my boss!",
|
|
79
|
+
]
|
|
@@ -9,14 +9,16 @@ import re
|
|
|
9
9
|
import secrets
|
|
10
10
|
import sys
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
def hash_password(password, salt=None):
|
|
13
14
|
if salt is None:
|
|
14
15
|
salt = secrets.token_bytes(16)
|
|
15
16
|
elif isinstance(salt, str):
|
|
16
17
|
salt = bytes.fromhex(salt)
|
|
17
|
-
pwd_hash = hashlib.pbkdf2_hmac(
|
|
18
|
+
pwd_hash = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 600000)
|
|
18
19
|
return pwd_hash.hex(), salt.hex()
|
|
19
20
|
|
|
21
|
+
|
|
20
22
|
def verify_password(stored_hash: str, stored_salt: str, password: str) -> bool:
|
|
21
23
|
if not stored_hash or not stored_salt or not password:
|
|
22
24
|
return False
|
|
@@ -27,6 +29,7 @@ def verify_password(stored_hash: str, stored_salt: str, password: str) -> bool:
|
|
|
27
29
|
except (ValueError, TypeError):
|
|
28
30
|
return False
|
|
29
31
|
|
|
32
|
+
|
|
30
33
|
def setup_acc() -> bool:
|
|
31
34
|
print("\nWelcome to Flyshell, New User!")
|
|
32
35
|
print("Please follow the prompts to create your Flyshell profile.")
|
|
@@ -58,7 +61,7 @@ def setup_acc() -> bool:
|
|
|
58
61
|
has_upper = any(char.isupper() for char in password)
|
|
59
62
|
has_lower = any(char.islower() for char in password)
|
|
60
63
|
has_digit = any(char.isdigit() for char in password)
|
|
61
|
-
has_speci = bool(re.search(r
|
|
64
|
+
has_speci = bool(re.search(r"[^a-zA-Z0-9]", password))
|
|
62
65
|
if len(password) >= 10 and has_upper and has_lower and has_digit and has_speci:
|
|
63
66
|
break
|
|
64
67
|
else:
|
|
@@ -70,15 +73,11 @@ def setup_acc() -> bool:
|
|
|
70
73
|
print(f"You have {i} attempts remaining.\n")
|
|
71
74
|
continue
|
|
72
75
|
pwd_hash, salt = hash_password(password)
|
|
73
|
-
auth_data = {
|
|
74
|
-
"username": username,
|
|
75
|
-
"hash": pwd_hash,
|
|
76
|
-
"salt": salt
|
|
77
|
-
}
|
|
78
76
|
data.set_auth(username, pwd_hash, salt)
|
|
79
77
|
print("\nSUCCESS: Account created successfully!")
|
|
80
78
|
return True
|
|
81
79
|
|
|
80
|
+
|
|
82
81
|
def login_flow(is_lock=False) -> bool:
|
|
83
82
|
auth_info = data.get_auth()
|
|
84
83
|
if not auth_info or "hash" not in auth_info:
|
|
@@ -89,7 +88,7 @@ def login_flow(is_lock=False) -> bool:
|
|
|
89
88
|
stored_hash = auth_info.get("hash")
|
|
90
89
|
stored_salt = auth_info.get("salt")
|
|
91
90
|
if is_lock:
|
|
92
|
-
input(
|
|
91
|
+
input("\nFlyshell is locked. To unlock, press ENTER.")
|
|
93
92
|
print(f"\nWelcome back, {stored_user}!")
|
|
94
93
|
else:
|
|
95
94
|
print(f"\nWelcome, {stored_user}!")
|
|
@@ -101,12 +100,13 @@ def login_flow(is_lock=False) -> bool:
|
|
|
101
100
|
print()
|
|
102
101
|
return True
|
|
103
102
|
i -= 1
|
|
104
|
-
print(
|
|
103
|
+
print("\nAccount Error: Password does not match.")
|
|
105
104
|
if i == 0:
|
|
106
105
|
print("Too many attempts.\n")
|
|
107
106
|
return False
|
|
108
107
|
print(f"You have {i} attempts remaining.\n")
|
|
109
108
|
|
|
109
|
+
|
|
110
110
|
class Lock(BaseCmd):
|
|
111
111
|
name = "lock"
|
|
112
112
|
description = "Lock the Flyshell system"
|
|
@@ -119,4 +119,4 @@ class Lock(BaseCmd):
|
|
|
119
119
|
return (0, "")
|
|
120
120
|
else:
|
|
121
121
|
print("\nShutting down...\n")
|
|
122
|
-
sys.exit(0)
|
|
122
|
+
sys.exit(0)
|