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.
Files changed (42) hide show
  1. {flyshell-1.2.5/src/flyshell.egg-info → flyshell-1.2.6}/PKG-INFO +1 -1
  2. flyshell-1.2.6/src/flyshell/builtin/pkg.py +33 -0
  3. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/constants.py +3 -2
  4. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/core/directory.py +2 -1
  5. flyshell-1.2.6/src/flyshell/core/env.py +129 -0
  6. {flyshell-1.2.5 → flyshell-1.2.6/src/flyshell.egg-info}/PKG-INFO +1 -1
  7. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell.egg-info/SOURCES.txt +2 -0
  8. {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_env_keys.py +38 -0
  9. flyshell-1.2.6/tests/test_pkg.py +50 -0
  10. flyshell-1.2.5/src/flyshell/core/env.py +0 -40
  11. {flyshell-1.2.5 → flyshell-1.2.6}/LICENSE +0 -0
  12. {flyshell-1.2.5 → flyshell-1.2.6}/README.md +0 -0
  13. {flyshell-1.2.5 → flyshell-1.2.6}/pyproject.toml +0 -0
  14. {flyshell-1.2.5 → flyshell-1.2.6}/setup.cfg +0 -0
  15. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/__init__.py +0 -0
  16. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/abstract/__init__.py +0 -0
  17. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/abstract/base_cmd.py +0 -0
  18. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/abstract/base_plugin.py +0 -0
  19. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/abstract/parent_cmd.py +0 -0
  20. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/builtin/__init__.py +0 -0
  21. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/builtin/diskops.py +0 -0
  22. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/builtin/fs.py +0 -0
  23. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/builtin/system.py +0 -0
  24. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/builtin/utils.py +0 -0
  25. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/core/__init__.py +0 -0
  26. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/core/auth.py +0 -0
  27. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/core/console.py +0 -0
  28. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/core/data.py +0 -0
  29. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/core/loader.py +0 -0
  30. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell/main.py +0 -0
  31. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell.egg-info/dependency_links.txt +0 -0
  32. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell.egg-info/entry_points.txt +0 -0
  33. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell.egg-info/requires.txt +0 -0
  34. {flyshell-1.2.5 → flyshell-1.2.6}/src/flyshell.egg-info/top_level.txt +0 -0
  35. {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_auth.py +0 -0
  36. {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_data.py +0 -0
  37. {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_directory.py +0 -0
  38. {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_diskops.py +0 -0
  39. {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_fs.py +0 -0
  40. {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_loader.py +0 -0
  41. {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_security.py +0 -0
  42. {flyshell-1.2.5 → flyshell-1.2.6}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flyshell
3
- Version: 1.2.5
3
+ Version: 1.2.6
4
4
  Summary: A modular, zero-dependency command-line environment in Python.
5
5
  License: MIT
6
6
  Requires-Python: >=3.10
@@ -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 = 130
9
- VERSION = "1.2.5"
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))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flyshell
3
- Version: 1.2.5
3
+ Version: 1.2.6
4
4
  Summary: A modular, zero-dependency command-line environment in Python.
5
5
  License: MIT
6
6
  Requires-Python: >=3.10
@@ -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