flyshell 1.2.4__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.4/src/flyshell.egg-info → flyshell-1.2.6}/PKG-INFO +1 -1
  2. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/abstract/base_plugin.py +7 -1
  3. flyshell-1.2.6/src/flyshell/builtin/pkg.py +33 -0
  4. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/constants.py +3 -2
  5. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/core/directory.py +12 -1
  6. flyshell-1.2.6/src/flyshell/core/env.py +129 -0
  7. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/core/loader.py +19 -6
  8. {flyshell-1.2.4 → flyshell-1.2.6/src/flyshell.egg-info}/PKG-INFO +1 -1
  9. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell.egg-info/SOURCES.txt +3 -0
  10. flyshell-1.2.6/tests/test_env_keys.py +153 -0
  11. flyshell-1.2.6/tests/test_pkg.py +50 -0
  12. flyshell-1.2.4/src/flyshell/core/env.py +0 -9
  13. {flyshell-1.2.4 → flyshell-1.2.6}/LICENSE +0 -0
  14. {flyshell-1.2.4 → flyshell-1.2.6}/README.md +0 -0
  15. {flyshell-1.2.4 → flyshell-1.2.6}/pyproject.toml +0 -0
  16. {flyshell-1.2.4 → flyshell-1.2.6}/setup.cfg +0 -0
  17. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/__init__.py +0 -0
  18. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/abstract/__init__.py +0 -0
  19. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/abstract/base_cmd.py +0 -0
  20. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/abstract/parent_cmd.py +0 -0
  21. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/builtin/__init__.py +0 -0
  22. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/builtin/diskops.py +0 -0
  23. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/builtin/fs.py +0 -0
  24. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/builtin/system.py +0 -0
  25. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/builtin/utils.py +0 -0
  26. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/core/__init__.py +0 -0
  27. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/core/auth.py +0 -0
  28. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/core/console.py +0 -0
  29. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/core/data.py +0 -0
  30. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell/main.py +0 -0
  31. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell.egg-info/dependency_links.txt +0 -0
  32. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell.egg-info/entry_points.txt +0 -0
  33. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell.egg-info/requires.txt +0 -0
  34. {flyshell-1.2.4 → flyshell-1.2.6}/src/flyshell.egg-info/top_level.txt +0 -0
  35. {flyshell-1.2.4 → flyshell-1.2.6}/tests/test_auth.py +0 -0
  36. {flyshell-1.2.4 → flyshell-1.2.6}/tests/test_data.py +0 -0
  37. {flyshell-1.2.4 → flyshell-1.2.6}/tests/test_directory.py +0 -0
  38. {flyshell-1.2.4 → flyshell-1.2.6}/tests/test_diskops.py +0 -0
  39. {flyshell-1.2.4 → flyshell-1.2.6}/tests/test_fs.py +0 -0
  40. {flyshell-1.2.4 → flyshell-1.2.6}/tests/test_loader.py +0 -0
  41. {flyshell-1.2.4 → flyshell-1.2.6}/tests/test_security.py +0 -0
  42. {flyshell-1.2.4 → 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.4
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
@@ -12,14 +12,20 @@ class BasePlugin:
12
12
  identifier: str = ""
13
13
  usage: str = ""
14
14
  extended_help: str = ""
15
- requires_api = False
15
+ requires_api: bool = False
16
+ api_keys: list = []
16
17
 
17
18
  def __init__(self, context: dict):
18
19
  self.context = context
19
20
  self.plugin_name = context.get("plugin_name", self.name)
20
21
  self.storage = context.get("storage", {})
22
+ self.api_keys_data = context.get("api_keys", {})
23
+ self.env_file_path = context.get("env_path", None)
21
24
  self.on_load()
22
25
 
26
+ def get_api_key(self, key_name: str, default=None):
27
+ return self.api_keys_data.get(key_name, default)
28
+
23
29
  def on_load(self):
24
30
  pass
25
31
 
@@ -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 = 129
9
- VERSION = "1.2.4"
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(),
@@ -160,6 +161,16 @@ def _execute_single(raw_cmd: str) -> int:
160
161
  elif cmd_name in c.PLUGINS:
161
162
  log(raw_cmd)
162
163
  plugin = c.PLUGINS[cmd_name]
164
+ required_keys = getattr(plugin, "api_keys", [])
165
+ available_keys = getattr(plugin, "api_keys_data", {})
166
+ missing = [k for k in required_keys if k not in available_keys]
167
+ if missing:
168
+ env_path = getattr(plugin, "env_file_path", "flyshell_keys.env")
169
+ print(
170
+ f"\nPlugin Error: Cannot execute '{cmd_name}'. Missing required API key(s): {', '.join(missing)}"
171
+ )
172
+ print(f"Define them in {env_path} and retry.\n")
173
+ return 1
163
174
  try:
164
175
  result = plugin.execute(args)
165
176
  return result if isinstance(result, int) else 0
@@ -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))
@@ -2,7 +2,7 @@
2
2
 
3
3
  from flyshell import constants as c
4
4
  from flyshell.abstract.base_plugin import BasePlugin
5
- from flyshell.core import data
5
+ from flyshell.core import data, env
6
6
  from flyshell.core.directory import RESERVED_NAMES
7
7
 
8
8
  from pathlib import Path
@@ -50,15 +50,24 @@ def scan_plugins(plugin_folder=None):
50
50
  for _, obj in inspect.getmembers(module, inspect.isclass):
51
51
  if issubclass(obj, BasePlugin) and obj is not BasePlugin:
52
52
  resolved_name = getattr(obj, "identifier", None) or default_name
53
-
54
- # Also verify the resolved identifier doesn't conflict with a reserved name
55
53
  if resolved_name in RESERVED_NAMES:
56
54
  print(
57
55
  f"\n⚠️ Collision Error - Plugin '{resolved_name}' conflicts with a built-in Flyshell command and will not be loaded."
58
56
  )
59
57
  break
60
-
61
- ctx = build_context(resolved_name, file_path)
58
+ ctx = build_context(resolved_name, default_name, file_path)
59
+ required_keys = getattr(obj, "api_keys", [])
60
+ found_keys = ctx["api_keys"]
61
+ missing_keys = [k for k in required_keys if k not in found_keys]
62
+ if missing_keys:
63
+ env_file = ctx["env_path"]
64
+ print(
65
+ f"\n⚠️ API Key Error - Plugin '{resolved_name}' is missing required key(s): {', '.join(missing_keys)}"
66
+ )
67
+ print(f"Please define them in: {env_file} as:")
68
+ for mk in missing_keys:
69
+ print(f"{default_name}_{mk}=<your_key>")
70
+ break
62
71
  instance = obj(ctx)
63
72
  candidates.setdefault(resolved_name, []).append(
64
73
  (instance, file_path)
@@ -92,11 +101,15 @@ def scan_plugins(plugin_folder=None):
92
101
  print("\nNo plugins loaded.")
93
102
 
94
103
 
95
- def build_context(plugin_name, file_path=None):
104
+ def build_context(plugin_name, default_name, file_path=None):
96
105
  plugin_storage = data.get_all_plugin_data(plugin_name)
106
+ api_keys = env.get_plugin_api_keys(str(default_name))
97
107
  context = {
98
108
  "plugin_name": plugin_name,
109
+ "plugin_dir_name": str(default_name),
99
110
  "file_path": str(file_path) if file_path else None,
111
+ "env_path": str(env.get_env_path()),
112
+ "api_keys": api_keys,
100
113
  "metadata": {"host_os": c.HOST_OS, "version": c.VERSION, "build": c.BUILD},
101
114
  "storage": plugin_storage,
102
115
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flyshell
3
- Version: 1.2.4
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
@@ -30,7 +31,9 @@ tests/test_auth.py
30
31
  tests/test_data.py
31
32
  tests/test_directory.py
32
33
  tests/test_diskops.py
34
+ tests/test_env_keys.py
33
35
  tests/test_fs.py
34
36
  tests/test_loader.py
37
+ tests/test_pkg.py
35
38
  tests/test_security.py
36
39
  tests/test_utils.py
@@ -0,0 +1,153 @@
1
+ # tests\test_env_keys.py
2
+
3
+ # tests/test_env_api_keys.py
4
+
5
+ from flyshell import constants as c
6
+ from flyshell.abstract.base_plugin import BasePlugin
7
+ from flyshell.core import directory, env, loader
8
+
9
+ import pytest
10
+
11
+
12
+ @pytest.fixture
13
+ def mock_env_file(tmp_path, monkeypatch):
14
+ """Sets up a temporary flyshell_keys.env and points env/c.APP_DIR to it."""
15
+ env_file = tmp_path / "flyshell_keys.env"
16
+ monkeypatch.setattr(c, "APP_DIR", tmp_path)
17
+ monkeypatch.setattr(env, "get_env_path", lambda: env_file)
18
+ return env_file
19
+
20
+
21
+ def test_env_read_and_key_parsing(mock_env_file):
22
+ mock_env_file.write_text(
23
+ "# Comments should be ignored\n"
24
+ "sample_api_token=secret_123\n"
25
+ 'sample_secondary_key="another_val"\n'
26
+ "otherplugin_key=val\n"
27
+ "invalid_line_without_equals\n"
28
+ )
29
+ keys = env.get_plugin_api_keys("sample")
30
+ assert keys == {
31
+ "api_token": "secret_123",
32
+ "secondary_key": "another_val",
33
+ }
34
+ assert "key" not in keys
35
+
36
+
37
+ def test_extra_keys_are_included(mock_env_file):
38
+ mock_env_file.write_text(
39
+ "myplug_required_one=abc\n"
40
+ "myplug_bonus_feature=xyz\n"
41
+ "myplug_third_param=12345\n"
42
+ )
43
+ keys = env.get_plugin_api_keys("myplug")
44
+ assert keys == {
45
+ "required_one": "abc",
46
+ "bonus_feature": "xyz",
47
+ "third_param": "12345",
48
+ }
49
+
50
+
51
+ def test_loader_populates_context_correctly(tmp_path, mock_env_file, monkeypatch):
52
+ mock_env_file.write_text("dummy_token=token123\n")
53
+ plugin_file = tmp_path / "dummy.py"
54
+ plugin_file.write_text(
55
+ "from flyshell.abstract.base_plugin import BasePlugin\n"
56
+ "class DummyPlugin(BasePlugin):\n"
57
+ " api_keys = ['token']\n"
58
+ " def execute(self, args): return 0\n"
59
+ )
60
+ monkeypatch.setattr(c, "USER_PLUGIN_DIR", tmp_path)
61
+ loader.scan_plugins(plugin_folder=tmp_path)
62
+ assert "dummy" in c.PLUGINS
63
+ plugin = c.PLUGINS["dummy"]
64
+ assert plugin.context["plugin_name"] == "dummy"
65
+ assert plugin.context["plugin_dir_name"] == "dummy"
66
+ assert plugin.context["file_path"] == str(plugin_file)
67
+ assert plugin.context["env_path"] == str(mock_env_file)
68
+ assert plugin.api_keys_data == {"token": "token123"}
69
+ assert plugin.get_api_key("token") == "token123"
70
+
71
+
72
+ def test_loader_rejects_plugin_with_missing_keys(
73
+ tmp_path, mock_env_file, monkeypatch, capsys
74
+ ):
75
+ mock_env_file.write_text("needkey_first=val1\n")
76
+ plugin_file = tmp_path / "needkey.py"
77
+ plugin_file.write_text(
78
+ "from flyshell.abstract.base_plugin import BasePlugin\n"
79
+ "class NeedKeyPlugin(BasePlugin):\n"
80
+ " api_keys = ['first', 'second']\n"
81
+ " def execute(self, args): return 0\n"
82
+ )
83
+ monkeypatch.setattr(c, "USER_PLUGIN_DIR", tmp_path)
84
+ loader.scan_plugins(plugin_folder=tmp_path)
85
+ assert "needkey" not in c.PLUGINS
86
+ out = capsys.readouterr().out
87
+ assert "API Key Error" in out
88
+ assert "second" in out
89
+
90
+
91
+ def test_directory_execute_fallback_when_keys_missing(
92
+ mock_env_file, monkeypatch, capsys
93
+ ):
94
+ class IncompletePlugin(BasePlugin):
95
+ api_keys = ["secret_key"]
96
+ executed = False
97
+
98
+ def execute(self, args):
99
+ self.executed = True
100
+ return 0
101
+
102
+ ctx = {
103
+ "plugin_name": "broken_keys",
104
+ "plugin_dir_name": "broken_keys",
105
+ "file_path": None,
106
+ "env_path": str(mock_env_file),
107
+ "api_keys": {},
108
+ }
109
+ instance = IncompletePlugin(ctx)
110
+ monkeypatch.setitem(c.PLUGINS, "broken_keys", instance)
111
+ exit_code = directory._execute_single("broken_keys")
112
+ assert exit_code == 1
113
+ assert instance.executed is False
114
+ out = capsys.readouterr().out
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,9 +0,0 @@
1
- # src\flyshell\core\env.py
2
-
3
- from flyshell import constants as c
4
-
5
-
6
- def create_env():
7
- env_path = c.APP_DIR / "flyshell_keys.env"
8
- if not env_path.exists():
9
- env_path.touch()
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