From 3020e77c933f9a6ac8e230a993323edee5476368 Mon Sep 17 00:00:00 2001 From: ANSHIKATYAGI30 Date: Sat, 6 Jun 2026 13:27:03 +0530 Subject: [PATCH 1/7] fix: cross-platform-config --- plugins/gh/src/plugin.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/plugins/gh/src/plugin.py b/plugins/gh/src/plugin.py index c93021c7..77ba3e19 100644 --- a/plugins/gh/src/plugin.py +++ b/plugins/gh/src/plugin.py @@ -16,7 +16,17 @@ def get_config_path() -> str: - return os.path.join(os.environ.get("APPDATA", ""), "GitHub CLI", "config.yml") + appdata = os.environ.get("APPDATA") + + if appdata: + return os.path.join(appdata, "GitHub CLI", "config.yml") + + return os.path.join( + os.path.expanduser("~"), + ".config", + "gh", + "config.yml", + ) def log(message: str) -> None: @@ -32,8 +42,10 @@ def read_yaml(file_path: str) -> dict: return {} with open(file_path, "r", encoding="utf-8") as file_handle: - data = yaml.safe_load(file_handle) - return data if isinstance(data, dict) else {} + data = yaml.safe_load(file_handle) or {} + if not isinstance(data, dict): + return {} + return data def write_yaml(file_path: str, data: dict) -> None: @@ -49,7 +61,7 @@ def merge_settings(target: dict, source: dict) -> bool: changed = False for key, value in source.items(): - if value == "": + if value == None: continue current_value = target.get(key) @@ -87,7 +99,7 @@ def check_installed(request_id: str) -> dict: def apply_config(request_id: str, args: dict, context: dict) -> dict: dry_run = bool(context.get("dryRun", False)) - updates = {key: value for key, value in args.items() if key != "dry_run"} + updates = {key: value for key, value in args.items() if key != "dryRun"} config_path = get_config_path() if yaml is None: From c758c323bec976ea396e9b1d19829d3d1269fe1f Mon Sep 17 00:00:00 2001 From: ANSHIKATYAGI30 Date: Sat, 6 Jun 2026 23:38:13 +0530 Subject: [PATCH 2/7] fix: protocol issues --- plugins/gh/src/plugin.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/plugins/gh/src/plugin.py b/plugins/gh/src/plugin.py index 77ba3e19..12f1953e 100644 --- a/plugins/gh/src/plugin.py +++ b/plugins/gh/src/plugin.py @@ -87,18 +87,12 @@ def get_config_target(config: dict) -> dict: return config -def check_installed(request_id: str) -> dict: - installed = shutil.which("gh") is not None or shutil.which("gh.exe") is not None - return { - "requestId": request_id, - "success": True, - "changed": False, - "data": installed, - } +def check_installed() -> bool: + return ( shutil.which("gh") is not None or shutil.which("gh.exe") is not None ) def apply_config(request_id: str, args: dict, context: dict) -> dict: - dry_run = bool(context.get("dryRun", False)) + dry_run = bool(args.get("dryRun", False)) updates = {key: value for key, value in args.items() if key != "dryRun"} config_path = get_config_path() @@ -116,7 +110,6 @@ def apply_config(request_id: str, args: dict, context: dict) -> dict: log(f"dry_run: no changes for {config_path}") return { "requestId": request_id, - "success": True, "changed": changed, } @@ -125,19 +118,19 @@ def apply_config(request_id: str, args: dict, context: dict) -> dict: return { "requestId": request_id, - "success": True, "changed": changed, } def handle(request: dict) -> dict: - request_id = request.get("requestId", "unknown") + request_id = request.get("requestId") or "unknown" command = request.get("command") args = request.get("args", {}) context = request.get("context", {}) - if command == "check_installed": - return check_installed(request_id) + if command == "check_installed": + installed = check_installed() + return { "requestId": request_id, "installed": installed, } if command == "apply": if not isinstance(args, dict): raise ValueError("args must be an object") @@ -149,9 +142,12 @@ def handle(request: dict) -> dict: def main() -> None: - raw = sys.stdin.read() - if not raw: - return + raw = sys.stdin.read() + if not raw: + sys.stdout.write( + json.dumps({ "requestId": "unknown", "error": "No input received", }) + "\n" ) + sys.stdout.flush() + return try: request = json.loads(raw) From 31c9df7446e8d0baa7406f7829d20509d9c6aa5e Mon Sep 17 00:00:00 2001 From: ANSHIKATYAGI30 Date: Sun, 7 Jun 2026 13:54:06 +0530 Subject: [PATCH 3/7] fix: resolve main issues Signed-off-by: ANSHIKATYAGI30 --- plugins/gh/src/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/gh/src/plugin.py b/plugins/gh/src/plugin.py index 12f1953e..cbf50b1e 100644 --- a/plugins/gh/src/plugin.py +++ b/plugins/gh/src/plugin.py @@ -146,8 +146,8 @@ def main() -> None: if not raw: sys.stdout.write( json.dumps({ "requestId": "unknown", "error": "No input received", }) + "\n" ) - sys.stdout.flush() - return + sys.stdout.flush() + return try: request = json.loads(raw) From 6027055dc6611f2bdb4e7ad281405dbf3cac5020 Mon Sep 17 00:00:00 2001 From: ANSHIKATYAGI30 Date: Sun, 7 Jun 2026 19:56:38 +0530 Subject: [PATCH 4/7] fix-dry-run --- plugins/gh/src/plugin.py | 32 ++++++++++++-------- plugins/gh/test/test_gh.py | 61 +++++++++++++------------------------- 2 files changed, 40 insertions(+), 53 deletions(-) diff --git a/plugins/gh/src/plugin.py b/plugins/gh/src/plugin.py index cbf50b1e..d05a837e 100644 --- a/plugins/gh/src/plugin.py +++ b/plugins/gh/src/plugin.py @@ -61,7 +61,7 @@ def merge_settings(target: dict, source: dict) -> bool: changed = False for key, value in source.items(): - if value == None: + if value is None: continue current_value = target.get(key) @@ -91,9 +91,14 @@ def check_installed() -> bool: return ( shutil.which("gh") is not None or shutil.which("gh.exe") is not None ) -def apply_config(request_id: str, args: dict, context: dict) -> dict: - dry_run = bool(args.get("dryRun", False)) - updates = {key: value for key, value in args.items() if key != "dryRun"} +def apply_config(request_id: str, args: dict) -> dict: + dry_run = bool(args.get("dryRun", False)) + settings = args.get("settings", {}) + if not isinstance(settings, dict): + return { "requestId": request_id, + "error": "settings must be a dictionary", + } + updates = {key: value for key, value in settings.items()} config_path = get_config_path() if yaml is None: @@ -131,14 +136,17 @@ def handle(request: dict) -> dict: if command == "check_installed": installed = check_installed() return { "requestId": request_id, "installed": installed, } - if command == "apply": - if not isinstance(args, dict): - raise ValueError("args must be an object") - if not isinstance(context, dict): - raise ValueError("context must be an object") - return apply_config(request_id, args, context) + if command == "apply": + if not isinstance(args, dict): + return { "requestId": request_id, + "error": "args must be a dictionary", + } + return apply_config(request_id, args) - raise ValueError(f"Unknown command: {command}") + return { + "requestId": request_id, + "error": f"Unknown command: {command}", + } def main() -> None: @@ -157,8 +165,6 @@ def main() -> None: "requestId": request.get("requestId", "unknown") if "request" in locals() and isinstance(request, dict) else "unknown", - "success": False, - "changed": False, "error": str(error), } diff --git a/plugins/gh/test/test_gh.py b/plugins/gh/test/test_gh.py index 1243e1a9..b7c0ff9f 100644 --- a/plugins/gh/test/test_gh.py +++ b/plugins/gh/test/test_gh.py @@ -15,7 +15,7 @@ import yaml -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))) +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src"))) import plugin @@ -35,18 +35,14 @@ def test_check_installed_returns_true_when_gh_is_found(self): response = self.run_main({"requestId": "req-1", "command": "check_installed", "args": {}}) self.assertEqual(response["requestId"], "req-1") - self.assertTrue(response["success"]) - self.assertFalse(response["changed"]) - self.assertTrue(response["data"]) + self.assertTrue(response["installed"]) def test_check_installed_returns_false_when_gh_is_missing(self): with patch("plugin.shutil.which", return_value=None): response = self.run_main({"requestId": "req-2", "command": "check_installed", "args": {}}) self.assertEqual(response["requestId"], "req-2") - self.assertTrue(response["success"]) - self.assertFalse(response["changed"]) - self.assertFalse(response["data"]) + self.assertFalse(response["installed"]) def test_apply_writes_merged_config_and_returns_changed_true(self): with tempfile.TemporaryDirectory() as tmp_dir: @@ -67,20 +63,18 @@ def test_apply_writes_merged_config_and_returns_changed_true(self): "requestId": "req-3", "command": "apply", "args": { - "git_protocol": "https", - "editor": "code --wait", - "prompt": "enabled", - "pager": "less", - "http_unix_socket": "", - "browser": "", - }, - "context": {"dryRun": False}, + "dryRun": False, + "settings": { + "git_protocol": "https", + "editor": "code --wait", + "prompt": "enabled", + "pager": "less", + }, + }, } ) self.assertEqual(response["requestId"], "req-3") - self.assertTrue(response["success"]) - self.assertTrue(response["changed"]) with open(config_path, "r", encoding="utf-8") as file_handle: content = yaml.safe_load(file_handle) @@ -118,20 +112,18 @@ def test_apply_with_no_changes_returns_changed_false(self): "requestId": "req-4", "command": "apply", "args": { - "git_protocol": "https", - "editor": "code --wait", - "prompt": "enabled", - "pager": "less", - "http_unix_socket": "", - "browser": "", + "dryRun": False, + "settings": { + "git_protocol": "https", + "editor": "code --wait", + "prompt": "enabled", + "pager": "less", + }, }, - "context": {"dryRun": False}, } ) self.assertEqual(response["requestId"], "req-4") - self.assertTrue(response["success"]) - self.assertFalse(response["changed"]) with open(config_path, "r", encoding="utf-8") as file_handle: content = yaml.safe_load(file_handle) @@ -147,14 +139,11 @@ def test_apply_with_dry_run_does_not_write_file(self): { "requestId": "req-5", "command": "apply", - "args": {"git_protocol": "https", "dry_run": True}, - "context": {"dryRun": True}, + "args": { "dryRun": True, "settings": { "git_protocol": "https" } }, } ) self.assertEqual(response["requestId"], "req-5") - self.assertTrue(response["success"]) - self.assertTrue(response["changed"]) self.assertFalse(os.path.exists(config_path)) def test_apply_creates_missing_directory(self): @@ -167,14 +156,11 @@ def test_apply_creates_missing_directory(self): { "requestId": "req-6", "command": "apply", - "args": {"git_protocol": "https"}, - "context": {"dryRun": False}, + "args": { "dryRun": False, "settings": {"git_protocol": "https" },}, } ) self.assertEqual(response["requestId"], "req-6") - self.assertTrue(response["success"]) - self.assertTrue(response["changed"]) self.assertTrue(os.path.isdir(os.path.dirname(config_path))) self.assertTrue(os.path.exists(config_path)) @@ -184,14 +170,11 @@ def test_apply_returns_error_when_pyyaml_is_missing(self): { "requestId": "req-7", "command": "apply", - "args": {"git_protocol": "https"}, - "context": {"dryRun": False}, + "args": { "dryRun": False, "settings": { "git_protocol": "https" }, }, } ) self.assertEqual(response["requestId"], "req-7") - self.assertFalse(response["success"]) - self.assertFalse(response["changed"]) self.assertIn("PyYAML", response["error"]) def test_unknown_command_returns_error(self): @@ -204,8 +187,6 @@ def test_unknown_command_returns_error(self): } ) self.assertEqual(response["requestId"], "req-8") - self.assertFalse(response["success"]) - self.assertFalse(response["changed"]) self.assertIn("Unknown command", response["error"]) From 0e6f3fe829d23eb51563b58f99b3c264902f8135 Mon Sep 17 00:00:00 2001 From: ANSHIKATYAGI30 Date: Thu, 11 Jun 2026 12:44:54 +0530 Subject: [PATCH 5/7] fix-atomic-write-issue --- plugins/gh/src/plugin.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/gh/src/plugin.py b/plugins/gh/src/plugin.py index d05a837e..c20ed4ea 100644 --- a/plugins/gh/src/plugin.py +++ b/plugins/gh/src/plugin.py @@ -8,6 +8,7 @@ import os import shutil import sys +import tempfile try: import yaml @@ -53,8 +54,15 @@ def write_yaml(file_path: str, data: dict) -> None: raise RuntimeError("PyYAML is required to read or write gh config") os.makedirs(os.path.dirname(file_path), exist_ok=True) - with open(file_path, "w", encoding="utf-8") as file_handle: - yaml.dump(data, file_handle, default_flow_style=False, sort_keys=False) + dir_name = os.path.dirname(file_path) or "." + fd, tmp_path = tempfile.mkstemp(dir=dir_name, suffix=".yml") + try: + with os.fdopen(fd, "w", encoding="utf-8") as f: + yaml.dump(data, f, default_flow_style=False) + os.replace(tmp_path, file_path) + except Exception: + os.unlink(tmp_path) + raise def merge_settings(target: dict, source: dict) -> bool: From 97f34788eeeeba72833cfbd2543e71074b3479e4 Mon Sep 17 00:00:00 2001 From: ANSHIKATYAGI30 Date: Fri, 12 Jun 2026 20:10:58 +0530 Subject: [PATCH 6/7] fix: add assertations and fix lint issues --- plugins/gh/src/plugin.py | 39 +++++++++++++++++++------------------- plugins/gh/test/test_gh.py | 9 ++++++++- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/plugins/gh/src/plugin.py b/plugins/gh/src/plugin.py index c20ed4ea..9d744b6f 100644 --- a/plugins/gh/src/plugin.py +++ b/plugins/gh/src/plugin.py @@ -95,16 +95,16 @@ def get_config_target(config: dict) -> dict: return config -def check_installed() -> bool: +def check_installed() -> bool: return ( shutil.which("gh") is not None or shutil.which("gh.exe") is not None ) -def apply_config(request_id: str, args: dict) -> dict: - dry_run = bool(args.get("dryRun", False)) - settings = args.get("settings", {}) - if not isinstance(settings, dict): - return { "requestId": request_id, - "error": "settings must be a dictionary", +def apply_config(request_id: str, args: dict) -> dict: + dry_run = bool(args.get("dryRun", False)) + settings = args.get("settings", {}) + if not isinstance(settings, dict): + return { "requestId": request_id, + "error": "settings must be a dictionary", } updates = {key: value for key, value in settings.items()} @@ -139,16 +139,15 @@ def handle(request: dict) -> dict: request_id = request.get("requestId") or "unknown" command = request.get("command") args = request.get("args", {}) - context = request.get("context", {}) - if command == "check_installed": - installed = check_installed() + if command == "check_installed": + installed = check_installed() return { "requestId": request_id, "installed": installed, } - if command == "apply": - if not isinstance(args, dict): - return { "requestId": request_id, - "error": "args must be a dictionary", - } + if command == "apply": + if not isinstance(args, dict): + return { "requestId": request_id, + "error": "args must be a dictionary", + } return apply_config(request_id, args) return { @@ -158,11 +157,11 @@ def handle(request: dict) -> dict: def main() -> None: - raw = sys.stdin.read() - if not raw: - sys.stdout.write( - json.dumps({ "requestId": "unknown", "error": "No input received", }) + "\n" ) - sys.stdout.flush() + raw = sys.stdin.read() + if not raw: + sys.stdout.write( + json.dumps({ "requestId": "unknown", "error": "No input received", }) + "\n" ) + sys.stdout.flush() return try: diff --git a/plugins/gh/test/test_gh.py b/plugins/gh/test/test_gh.py index b7c0ff9f..8ee5f933 100644 --- a/plugins/gh/test/test_gh.py +++ b/plugins/gh/test/test_gh.py @@ -35,6 +35,7 @@ def test_check_installed_returns_true_when_gh_is_found(self): response = self.run_main({"requestId": "req-1", "command": "check_installed", "args": {}}) self.assertEqual(response["requestId"], "req-1") + self.assertFalse(response["changed"]) self.assertTrue(response["installed"]) def test_check_installed_returns_false_when_gh_is_missing(self): @@ -42,6 +43,7 @@ def test_check_installed_returns_false_when_gh_is_missing(self): response = self.run_main({"requestId": "req-2", "command": "check_installed", "args": {}}) self.assertEqual(response["requestId"], "req-2") + self.assertFalse(response["changed"]) self.assertFalse(response["installed"]) def test_apply_writes_merged_config_and_returns_changed_true(self): @@ -75,6 +77,7 @@ def test_apply_writes_merged_config_and_returns_changed_true(self): ) self.assertEqual(response["requestId"], "req-3") + self.assertTrue(response["changed"]) with open(config_path, "r", encoding="utf-8") as file_handle: content = yaml.safe_load(file_handle) @@ -122,8 +125,8 @@ def test_apply_with_no_changes_returns_changed_false(self): }, } ) - self.assertEqual(response["requestId"], "req-4") + self.assertFalse(response["changed"]) with open(config_path, "r", encoding="utf-8") as file_handle: content = yaml.safe_load(file_handle) @@ -144,6 +147,7 @@ def test_apply_with_dry_run_does_not_write_file(self): ) self.assertEqual(response["requestId"], "req-5") + self.assertTrue(response["changed"]) self.assertFalse(os.path.exists(config_path)) def test_apply_creates_missing_directory(self): @@ -161,6 +165,7 @@ def test_apply_creates_missing_directory(self): ) self.assertEqual(response["requestId"], "req-6") + self.assertTrue(response["changed"]) self.assertTrue(os.path.isdir(os.path.dirname(config_path))) self.assertTrue(os.path.exists(config_path)) @@ -175,6 +180,7 @@ def test_apply_returns_error_when_pyyaml_is_missing(self): ) self.assertEqual(response["requestId"], "req-7") + self.assertFalse(response["changed"]) self.assertIn("PyYAML", response["error"]) def test_unknown_command_returns_error(self): @@ -187,6 +193,7 @@ def test_unknown_command_returns_error(self): } ) self.assertEqual(response["requestId"], "req-8") + self.assertFalse(response["changed"]) self.assertIn("Unknown command", response["error"]) From 533503fb02245da67e52606be6c24f388482d9e6 Mon Sep 17 00:00:00 2001 From: ANSHIKATYAGI30 Date: Fri, 12 Jun 2026 20:26:49 +0530 Subject: [PATCH 7/7] fix: write_yaml --- plugins/gh/src/plugin.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/plugins/gh/src/plugin.py b/plugins/gh/src/plugin.py index 9d744b6f..14acee22 100644 --- a/plugins/gh/src/plugin.py +++ b/plugins/gh/src/plugin.py @@ -54,14 +54,24 @@ def write_yaml(file_path: str, data: dict) -> None: raise RuntimeError("PyYAML is required to read or write gh config") os.makedirs(os.path.dirname(file_path), exist_ok=True) + dir_name = os.path.dirname(file_path) or "." fd, tmp_path = tempfile.mkstemp(dir=dir_name, suffix=".yml") + try: with os.fdopen(fd, "w", encoding="utf-8") as f: - yaml.dump(data, f, default_flow_style=False) - os.replace(tmp_path, file_path) + yaml.dump( + data, + f, + default_flow_style=False, + sort_keys=False, + ) + + os.replace(tmp_path, file_path) + except Exception: - os.unlink(tmp_path) + if os.path.exists(tmp_path): + os.unlink(tmp_path) raise