flyshell 1.2.5__tar.gz → 1.2.6__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.2.5/src/flyshell.egg-info → flyshell-1.2.6}/PKG-INFO +1 -1
- flyshell-1.2.6/src/flyshell/builtin/pkg.py +33 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/constants.py +3 -2
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/core/directory.py +2 -1
- flyshell-1.2.6/src/flyshell/core/env.py +129 -0
- {flyshell-1.2.5 → flyshell-1.2.6/src/flyshell.egg-info}/PKG-INFO +1 -1
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell.egg-info/SOURCES.txt +2 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_env_keys.py +38 -0
- flyshell-1.2.6/tests/test_pkg.py +50 -0
- flyshell-1.2.5/src/flyshell/core/env.py +0 -40
- {flyshell-1.2.5 → flyshell-1.2.6}/LICENSE +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/README.md +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/pyproject.toml +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/setup.cfg +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/__init__.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/abstract/__init__.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/abstract/base_cmd.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/abstract/base_plugin.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/abstract/parent_cmd.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/builtin/__init__.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/builtin/diskops.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/builtin/fs.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/builtin/system.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/builtin/utils.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/core/__init__.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/core/auth.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/core/console.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/core/data.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/core/loader.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/main.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell.egg-info/dependency_links.txt +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell.egg-info/entry_points.txt +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell.egg-info/requires.txt +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell.egg-info/top_level.txt +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_auth.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_data.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_directory.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_diskops.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_fs.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_loader.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_security.py +0 -0
- {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_utils.py +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# src\flyshell\builtin\pkg.py
|
|
2
|
+
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
from flyshell.abstract.base_cmd import BaseCmd
|
|
5
|
+
from flyshell.abstract.parent_cmd import ParentCmd
|
|
6
|
+
from flyshell.core import env
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PkgApi(BaseCmd):
|
|
10
|
+
name: str = "api"
|
|
11
|
+
description: str = "View configured package/plugin API keys"
|
|
12
|
+
min_args: int = 0
|
|
13
|
+
options: str = "Package name"
|
|
14
|
+
aliases: List[str] = ["keys"]
|
|
15
|
+
|
|
16
|
+
def execute(self, args: List[str]) -> Tuple[int, str]:
|
|
17
|
+
if not args:
|
|
18
|
+
code, output = env.get_all_keys()
|
|
19
|
+
else:
|
|
20
|
+
package_name = args[0]
|
|
21
|
+
code, output = env.get_plugin_keys_formatted(package_name)
|
|
22
|
+
return (code, f"\n{output}\n")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Pkg(ParentCmd):
|
|
26
|
+
name: str = "pkg"
|
|
27
|
+
description: str = "Manage Flyshell packages, plugins, and dependencies"
|
|
28
|
+
aliases: List[str] = ["package"]
|
|
29
|
+
|
|
30
|
+
def get_subcommands(self) -> List[BaseCmd]:
|
|
31
|
+
return [
|
|
32
|
+
PkgApi(),
|
|
33
|
+
]
|
|
@@ -5,8 +5,8 @@ import os
|
|
|
5
5
|
import platform
|
|
6
6
|
import time
|
|
7
7
|
|
|
8
|
-
BUILD =
|
|
9
|
-
VERSION = "1.2.
|
|
8
|
+
BUILD = 131
|
|
9
|
+
VERSION = "1.2.6"
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def _resolve_app_dir() -> Path:
|
|
@@ -53,6 +53,7 @@ REQUIRED_COMPONENTS = [
|
|
|
53
53
|
"builtin",
|
|
54
54
|
"builtin/diskops.py",
|
|
55
55
|
"builtin/fs.py",
|
|
56
|
+
"builtin/pkg.py",
|
|
56
57
|
"builtin/system.py",
|
|
57
58
|
"builtin/utils.py",
|
|
58
59
|
"constants.py",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from flyshell import constants as c
|
|
4
4
|
from flyshell.abstract.base_cmd import BaseCmd
|
|
5
5
|
from flyshell.abstract.parent_cmd import ParentCmd
|
|
6
|
-
from flyshell.builtin import diskops, fs, system, utils
|
|
6
|
+
from flyshell.builtin import diskops, fs, pkg, system, utils
|
|
7
7
|
from flyshell.core import auth, data
|
|
8
8
|
|
|
9
9
|
from datetime import datetime, timezone
|
|
@@ -64,6 +64,7 @@ INSTALLED_COMMANDS: list[BaseCmd] = [
|
|
|
64
64
|
diskops.MkDir(),
|
|
65
65
|
diskops.Move(),
|
|
66
66
|
system.OpenFile(),
|
|
67
|
+
pkg.Pkg(),
|
|
67
68
|
utils.Reboot(),
|
|
68
69
|
diskops.Rename(),
|
|
69
70
|
diskops.Rm(),
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# src/flyshell/core/env.py
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from flyshell import constants as c
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_env_path() -> Path:
|
|
8
|
+
primary = c.APP_DIR / "flyshell_keys.env"
|
|
9
|
+
if primary.exists():
|
|
10
|
+
return primary
|
|
11
|
+
fallback = c.APP_DIR / ".env"
|
|
12
|
+
if fallback.exists():
|
|
13
|
+
return fallback
|
|
14
|
+
return primary
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_env():
|
|
18
|
+
env_path = get_env_path()
|
|
19
|
+
if not env_path.exists():
|
|
20
|
+
env_path.touch()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def read_env_file() -> dict[str, str]:
|
|
24
|
+
env_path = get_env_path()
|
|
25
|
+
if not env_path.exists():
|
|
26
|
+
return {}
|
|
27
|
+
entries = {}
|
|
28
|
+
with open(env_path, "r", encoding="utf-8") as f:
|
|
29
|
+
for line in f:
|
|
30
|
+
line = line.strip()
|
|
31
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
32
|
+
continue
|
|
33
|
+
raw_k, raw_v = line.split("=", 1)
|
|
34
|
+
clean_k = raw_k.strip().replace("[", "").replace("]", "")
|
|
35
|
+
clean_v = raw_v.strip().strip("\"'").replace("[", "").replace("]", "")
|
|
36
|
+
if clean_k:
|
|
37
|
+
entries[clean_k] = clean_v
|
|
38
|
+
return entries
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _resolve_plugin_dir_name(target_name: str) -> tuple[str, object | None]:
|
|
42
|
+
"""Resolves an identifier, display name, or dir name to (dir_name, plugin_instance)."""
|
|
43
|
+
target_lower = target_name.strip().lower()
|
|
44
|
+
if hasattr(c, "PLUGINS"):
|
|
45
|
+
if target_lower in c.PLUGINS:
|
|
46
|
+
plugin = c.PLUGINS[target_lower]
|
|
47
|
+
dir_name = plugin.context.get("plugin_dir_name") or target_lower
|
|
48
|
+
dir_name = (
|
|
49
|
+
Path(dir_name).stem
|
|
50
|
+
if "\\" in str(dir_name) or "/" in str(dir_name)
|
|
51
|
+
else str(dir_name)
|
|
52
|
+
)
|
|
53
|
+
return (dir_name, plugin)
|
|
54
|
+
for cmd_name, plugin in c.PLUGINS.items():
|
|
55
|
+
p_display = getattr(plugin, "name", "").lower()
|
|
56
|
+
p_dir = str(plugin.context.get("plugin_dir_name", "")).lower()
|
|
57
|
+
p_dir_clean = Path(p_dir).stem
|
|
58
|
+
if target_lower in (cmd_name.lower(), p_display, p_dir, p_dir_clean):
|
|
59
|
+
resolved_dir = plugin.context.get("plugin_dir_name") or cmd_name
|
|
60
|
+
resolved_dir = (
|
|
61
|
+
Path(resolved_dir).stem
|
|
62
|
+
if "\\" in str(resolved_dir) or "/" in str(resolved_dir)
|
|
63
|
+
else str(resolved_dir)
|
|
64
|
+
)
|
|
65
|
+
return (resolved_dir, plugin)
|
|
66
|
+
return (target_name.strip(), None)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_plugin_api_keys(plugin_name_or_dir: str) -> dict[str, str]:
|
|
70
|
+
all_env = read_env_file()
|
|
71
|
+
dir_name, _ = _resolve_plugin_dir_name(plugin_name_or_dir)
|
|
72
|
+
prefixes = {
|
|
73
|
+
f"{dir_name.lower()}_",
|
|
74
|
+
f"{plugin_name_or_dir.strip().lower()}_",
|
|
75
|
+
}
|
|
76
|
+
plugin_keys = {}
|
|
77
|
+
for raw_k, val in all_env.items():
|
|
78
|
+
k_lower = raw_k.lower()
|
|
79
|
+
for prefix in prefixes:
|
|
80
|
+
if k_lower.startswith(prefix):
|
|
81
|
+
key_name = raw_k[len(prefix) :]
|
|
82
|
+
plugin_keys[key_name] = val
|
|
83
|
+
break
|
|
84
|
+
return plugin_keys
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_all_keys() -> tuple[int, str]:
|
|
88
|
+
"""Retrieve and display all configured API keys in the environment."""
|
|
89
|
+
env_path = get_env_path()
|
|
90
|
+
if not env_path.exists():
|
|
91
|
+
return (1, f"File Config Error: Environment file not found at '{env_path}'.")
|
|
92
|
+
try:
|
|
93
|
+
all_env = read_env_file()
|
|
94
|
+
if not all_env:
|
|
95
|
+
return (0, "No API keys configured.")
|
|
96
|
+
lines = ["Configured API keys:"]
|
|
97
|
+
for k, v in sorted(all_env.items()):
|
|
98
|
+
lines.append(f"{k} = {v}")
|
|
99
|
+
return (0, "\n".join(lines))
|
|
100
|
+
except Exception as e:
|
|
101
|
+
return (1, f"File Error: An unknown error occurred: {e}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_plugin_keys_formatted(target_name: str) -> tuple[int, str]:
|
|
105
|
+
if not target_name or not isinstance(target_name, str):
|
|
106
|
+
return (1, "Command Error: A valid plugin name must be provided.")
|
|
107
|
+
env_path = get_env_path()
|
|
108
|
+
if not env_path.exists():
|
|
109
|
+
return (1, f"File Config Error: Environment file not found at '{env_path}'.")
|
|
110
|
+
dir_name, plugin = _resolve_plugin_dir_name(target_name)
|
|
111
|
+
configured_keys = get_plugin_api_keys(target_name)
|
|
112
|
+
required_keys = getattr(plugin, "api_keys", []) if plugin else []
|
|
113
|
+
if not configured_keys and not required_keys:
|
|
114
|
+
return (0, f"No API keys found or required for plugin '{target_name}'.")
|
|
115
|
+
lines = [f"API Keys for plugin '{target_name}' (folder: '{dir_name}'):"]
|
|
116
|
+
lower_configured = {k.lower(): (k, v) for k, v in configured_keys.items()}
|
|
117
|
+
seen_keys = set()
|
|
118
|
+
for req in required_keys:
|
|
119
|
+
req_lower = req.lower()
|
|
120
|
+
seen_keys.add(req_lower)
|
|
121
|
+
if req_lower in lower_configured:
|
|
122
|
+
orig_k, val = lower_configured[req_lower]
|
|
123
|
+
lines.append(f"{orig_k} = {val} [Required]")
|
|
124
|
+
else:
|
|
125
|
+
lines.append(f"{req} = [MISSING / NOT SET] [Required]")
|
|
126
|
+
for k_lower, (orig_k, val) in sorted(lower_configured.items()):
|
|
127
|
+
if k_lower not in seen_keys:
|
|
128
|
+
lines.append(f"{orig_k} = {val} [Optional/Extra]")
|
|
129
|
+
return (0, "\n".join(lines))
|
|
@@ -17,6 +17,7 @@ src/flyshell/abstract/parent_cmd.py
|
|
|
17
17
|
src/flyshell/builtin/__init__.py
|
|
18
18
|
src/flyshell/builtin/diskops.py
|
|
19
19
|
src/flyshell/builtin/fs.py
|
|
20
|
+
src/flyshell/builtin/pkg.py
|
|
20
21
|
src/flyshell/builtin/system.py
|
|
21
22
|
src/flyshell/builtin/utils.py
|
|
22
23
|
src/flyshell/core/__init__.py
|
|
@@ -33,5 +34,6 @@ tests/test_diskops.py
|
|
|
33
34
|
tests/test_env_keys.py
|
|
34
35
|
tests/test_fs.py
|
|
35
36
|
tests/test_loader.py
|
|
37
|
+
tests/test_pkg.py
|
|
36
38
|
tests/test_security.py
|
|
37
39
|
tests/test_utils.py
|
|
@@ -113,3 +113,41 @@ def test_directory_execute_fallback_when_keys_missing(
|
|
|
113
113
|
assert instance.executed is False
|
|
114
114
|
out = capsys.readouterr().out
|
|
115
115
|
assert "Missing required API key(s): secret_key" in out
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_get_all_keys_success(mock_env_file):
|
|
119
|
+
mock_env_file.write_text("pluginA_token=123\npluginB_secret=xyz\n")
|
|
120
|
+
code, output = env.get_all_keys()
|
|
121
|
+
assert code == 0
|
|
122
|
+
assert "pluginA_token = 123" in output
|
|
123
|
+
assert "pluginB_secret = xyz" in output
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_get_all_keys_empty_file(mock_env_file):
|
|
127
|
+
mock_env_file.write_text("")
|
|
128
|
+
code, output = env.get_all_keys()
|
|
129
|
+
assert code == 0
|
|
130
|
+
assert "No API keys configured." in output
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_get_all_keys_missing_file(tmp_path, monkeypatch):
|
|
134
|
+
missing_path = tmp_path / "does_not_exist.env"
|
|
135
|
+
monkeypatch.setattr(env, "get_env_path", lambda: missing_path)
|
|
136
|
+
code, output = env.get_all_keys()
|
|
137
|
+
assert code == 1
|
|
138
|
+
assert "Environment file not found" in output
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_get_plugin_keys_formatted_success(mock_env_file):
|
|
142
|
+
mock_env_file.write_text("weather_api_key=token123\nweather_units=metric\n")
|
|
143
|
+
code, output = env.get_plugin_keys_formatted("weather")
|
|
144
|
+
assert code == 0
|
|
145
|
+
assert "weather" in output
|
|
146
|
+
assert "api_key = token123" in output
|
|
147
|
+
assert "units = metric" in output
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_get_plugin_keys_formatted_invalid_input():
|
|
151
|
+
code, output = env.get_plugin_keys_formatted("")
|
|
152
|
+
assert code == 1
|
|
153
|
+
assert "valid plugin name" in output
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# tests/test_pkg.py
|
|
2
|
+
|
|
3
|
+
from flyshell import constants as c
|
|
4
|
+
from flyshell.builtin import pkg
|
|
5
|
+
from flyshell.core import env
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def mock_env_file(tmp_path, monkeypatch):
|
|
12
|
+
c.PLUGINS.clear()
|
|
13
|
+
env_file = tmp_path / "flyshell_keys.env"
|
|
14
|
+
monkeypatch.setattr(c, "APP_DIR", tmp_path)
|
|
15
|
+
monkeypatch.setattr(env, "get_env_path", lambda: env_file)
|
|
16
|
+
yield env_file
|
|
17
|
+
c.PLUGINS.clear()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_pkg_dispatches_help_on_empty_args(capsys):
|
|
21
|
+
parent = pkg.Pkg()
|
|
22
|
+
code, msg = parent.execute([])
|
|
23
|
+
assert code == 0
|
|
24
|
+
out = capsys.readouterr().out
|
|
25
|
+
assert "Command Family: pkg" in out
|
|
26
|
+
assert "api (alias: keys)" in out
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_pkg_unknown_subcommand():
|
|
30
|
+
parent = pkg.Pkg()
|
|
31
|
+
code, msg = parent.execute(["unknown_cmd"])
|
|
32
|
+
assert code == 1
|
|
33
|
+
assert "Unknown subcommand 'unknown_cmd'" in msg
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_pkg_api_all_keys(mock_env_file):
|
|
37
|
+
mock_env_file.write_text("dummy_token=123\n")
|
|
38
|
+
parent = pkg.Pkg()
|
|
39
|
+
code, msg = parent.execute(["api"])
|
|
40
|
+
assert code == 0
|
|
41
|
+
assert "dummy_token = 123" in msg
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_pkg_api_plugin_keys(mock_env_file):
|
|
45
|
+
mock_env_file.write_text("weather_key=secret\nother_key=skip\n")
|
|
46
|
+
parent = pkg.Pkg()
|
|
47
|
+
code, msg = parent.execute(["api", "weather"])
|
|
48
|
+
assert code == 0
|
|
49
|
+
assert "key = secret" in msg
|
|
50
|
+
assert "other_key" not in msg
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# src\flyshell\core\env.py
|
|
2
|
-
|
|
3
|
-
from flyshell import constants as c
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def get_env_path() -> Path:
|
|
8
|
-
return c.APP_DIR / "flyshell_keys.env"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def create_env():
|
|
12
|
-
env_path = get_env_path()
|
|
13
|
-
if not env_path.exists():
|
|
14
|
-
env_path.touch()
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def read_env_file() -> dict[str, str]:
|
|
18
|
-
env_path = get_env_path()
|
|
19
|
-
if not env_path.exists():
|
|
20
|
-
return {}
|
|
21
|
-
entries = {}
|
|
22
|
-
with open(env_path, "r", encoding="utf-8") as f:
|
|
23
|
-
for line in f:
|
|
24
|
-
line = line.strip()
|
|
25
|
-
if not line or line.startswith("#") or "=" not in line:
|
|
26
|
-
continue
|
|
27
|
-
key, val = line.split("=", 1)
|
|
28
|
-
entries[key.strip()] = val.strip().strip("\"'")
|
|
29
|
-
return entries
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def get_plugin_api_keys(plugin_dir_name: str) -> dict[str, str]:
|
|
33
|
-
all_env = read_env_file()
|
|
34
|
-
prefix = f"{plugin_dir_name}_".lower()
|
|
35
|
-
plugin_keys = {}
|
|
36
|
-
for raw_k, val in all_env.items():
|
|
37
|
-
if raw_k.lower().startswith(prefix):
|
|
38
|
-
key_name = raw_k[len(prefix) :]
|
|
39
|
-
plugin_keys[key_name] = val
|
|
40
|
-
return plugin_keys
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|