ida-hcli 0.8.3.dev2__tar.gz → 0.8.3.dev4__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.
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/PKG-INFO +1 -1
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/pyproject.toml +1 -1
- ida_hcli-0.8.3.dev4/src/hcli/__init__.py +1 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/__init__.py +2 -2
- ida_hcli-0.8.3.dev4/src/hcli/commands/ke/__init__.py +16 -0
- ida_hcli-0.8.3.dev4/src/hcli/commands/ke/setup.py +221 -0
- ida_hcli-0.8.3.dev4/src/hcli/commands/ke/source/__init__.py +18 -0
- ida_hcli-0.8.3.dev4/src/hcli/commands/ke/source/add.py +35 -0
- ida_hcli-0.8.3.dev4/src/hcli/commands/ke/source/list.py +31 -0
- ida_hcli-0.8.3.dev4/src/hcli/commands/ke/source/remove.py +31 -0
- ida_hcli-0.8.3.dev4/src/hcli/commands/open.py +86 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/ida_hcli.egg-info/PKG-INFO +1 -1
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/ida_hcli.egg-info/SOURCES.txt +6 -0
- ida_hcli-0.8.3.dev2/src/hcli/__init__.py +0 -1
- ida_hcli-0.8.3.dev2/src/hcli/commands/open.py +0 -28
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/LICENSE +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/README.md +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/setup.cfg +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/auth/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/auth/default.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/auth/key/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/auth/key/create.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/auth/key/install.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/auth/key/list.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/auth/key/revoke.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/auth/list.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/auth/switch.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/commands.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/common.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/download.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/extension/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/extension/create.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/extension/list.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/ida/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/ida/install.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/license/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/license/common.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/license/get.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/license/install.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/license/list.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/login.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/logout.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/share/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/share/delete.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/share/get.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/share/list.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/share/put.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/update.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/commands/whoami.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/env.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/api/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/api/asset.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/api/auth.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/api/common.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/api/customer.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/api/index.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/api/keys.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/api/license.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/auth/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/commands/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/config/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/console.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/constants/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/constants/auth.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/constants/cli.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/extensions/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/ida/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/ida/python.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/update/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/update/release.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/update/version.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/util/__init__.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/util/crc32.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/util/io.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/util/output.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/util/python.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/lib/util/string.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/hcli/main.py +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/ida_hcli.egg-info/dependency_links.txt +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/ida_hcli.egg-info/entry_points.txt +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/ida_hcli.egg-info/requires.txt +0 -0
- {ida_hcli-0.8.3.dev2 → ida_hcli-0.8.3.dev4}/src/ida_hcli.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.3-dev.4"
|
|
@@ -10,14 +10,12 @@ def register_commands(cli: click.Group) -> None:
|
|
|
10
10
|
from .login import login
|
|
11
11
|
from .logout import logout
|
|
12
12
|
from .open import open_url
|
|
13
|
-
from .setup import setup
|
|
14
13
|
from .update import update
|
|
15
14
|
from .whoami import whoami
|
|
16
15
|
# placeholder for more commands
|
|
17
16
|
|
|
18
17
|
cli.add_command(login)
|
|
19
18
|
cli.add_command(logout)
|
|
20
|
-
cli.add_command(setup)
|
|
21
19
|
cli.add_command(whoami)
|
|
22
20
|
cli.add_command(update)
|
|
23
21
|
cli.add_command(download)
|
|
@@ -28,6 +26,7 @@ def register_commands(cli: click.Group) -> None:
|
|
|
28
26
|
from .auth import auth
|
|
29
27
|
from .extension import extension
|
|
30
28
|
from .ida import ida
|
|
29
|
+
from .ke import ke
|
|
31
30
|
from .license import license
|
|
32
31
|
from .share import share
|
|
33
32
|
|
|
@@ -36,3 +35,4 @@ def register_commands(cli: click.Group) -> None:
|
|
|
36
35
|
cli.add_command(share)
|
|
37
36
|
cli.add_command(license)
|
|
38
37
|
cli.add_command(extension)
|
|
38
|
+
cli.add_command(ke)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import rich_click as click
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.group()
|
|
7
|
+
def ke() -> None:
|
|
8
|
+
"""Knowledge Engine commands."""
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from .setup import setup # noqa: E402
|
|
13
|
+
from .source import source # noqa: E402
|
|
14
|
+
|
|
15
|
+
ke.add_command(source)
|
|
16
|
+
ke.add_command(setup)
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import tempfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import rich_click as click
|
|
11
|
+
|
|
12
|
+
from hcli.lib.commands import async_command
|
|
13
|
+
from hcli.lib.console import console
|
|
14
|
+
from hcli.lib.util.io import get_hcli_executable_path
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def setup_macos_protocol_handler() -> None:
|
|
18
|
+
"""Set up protocol handler for macOS using AppleScript and plist modification."""
|
|
19
|
+
try:
|
|
20
|
+
hcli_path = get_hcli_executable_path()
|
|
21
|
+
|
|
22
|
+
print(hcli_path)
|
|
23
|
+
|
|
24
|
+
# Create AppleScript application
|
|
25
|
+
applescript_content = f'''
|
|
26
|
+
on open location this_URL
|
|
27
|
+
do shell script "{hcli_path} open " & quoted form of this_URL
|
|
28
|
+
end open location
|
|
29
|
+
|
|
30
|
+
on run
|
|
31
|
+
-- This handler is called when the app is launched directly
|
|
32
|
+
end run
|
|
33
|
+
'''
|
|
34
|
+
|
|
35
|
+
# Create temporary directory for the AppleScript
|
|
36
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
37
|
+
script_path = Path(temp_dir) / "HCLIHandler.applescript"
|
|
38
|
+
app_path = Path.home() / "Applications" / "HCLIHandler.app"
|
|
39
|
+
|
|
40
|
+
# Write AppleScript
|
|
41
|
+
script_path.write_text(applescript_content)
|
|
42
|
+
|
|
43
|
+
# Compile AppleScript to application
|
|
44
|
+
subprocess.run(["osacompile", "-o", str(app_path), str(script_path)], check=True)
|
|
45
|
+
|
|
46
|
+
# Create Info.plist for the app to register URL scheme
|
|
47
|
+
info_plist_path = app_path / "Contents" / "Info.plist"
|
|
48
|
+
|
|
49
|
+
# Read existing plist
|
|
50
|
+
result = subprocess.run(
|
|
51
|
+
["plutil", "-convert", "xml1", "-o", "-", str(info_plist_path)],
|
|
52
|
+
capture_output=True,
|
|
53
|
+
text=True,
|
|
54
|
+
check=True,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
plist_content = result.stdout
|
|
58
|
+
|
|
59
|
+
# Add URL scheme handler to plist
|
|
60
|
+
url_scheme_xml = """
|
|
61
|
+
<key>CFBundleURLTypes</key>
|
|
62
|
+
<array>
|
|
63
|
+
<dict>
|
|
64
|
+
<key>CFBundleURLName</key>
|
|
65
|
+
<string>IDA URL Handler</string>
|
|
66
|
+
<key>CFBundleURLSchemes</key>
|
|
67
|
+
<array>
|
|
68
|
+
<string>ida</string>
|
|
69
|
+
</array>
|
|
70
|
+
</dict>
|
|
71
|
+
</array>"""
|
|
72
|
+
|
|
73
|
+
# Insert before closing </dict></plist>
|
|
74
|
+
if "<key>CFBundleURLTypes</key>" not in plist_content:
|
|
75
|
+
plist_content = plist_content.replace("</dict>\n</plist>", f"{url_scheme_xml}\n</dict>\n</plist>")
|
|
76
|
+
|
|
77
|
+
# Write back the modified plist
|
|
78
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".plist", delete=False) as temp_plist:
|
|
79
|
+
temp_plist.write(plist_content)
|
|
80
|
+
temp_plist_path = temp_plist.name
|
|
81
|
+
|
|
82
|
+
# Convert back to binary and replace original
|
|
83
|
+
subprocess.run(["plutil", "-convert", "binary1", temp_plist_path], check=True)
|
|
84
|
+
|
|
85
|
+
shutil.copy2(temp_plist_path, info_plist_path)
|
|
86
|
+
os.unlink(temp_plist_path)
|
|
87
|
+
|
|
88
|
+
# Register the app with Launch Services
|
|
89
|
+
subprocess.run(
|
|
90
|
+
[
|
|
91
|
+
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister",
|
|
92
|
+
"-f",
|
|
93
|
+
str(app_path),
|
|
94
|
+
],
|
|
95
|
+
check=True,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
console.print(f"[green]✓[/green] macOS protocol handler installed at {app_path}")
|
|
99
|
+
|
|
100
|
+
except subprocess.CalledProcessError as e:
|
|
101
|
+
console.print(f"[red]Failed to set up macOS protocol handler: {e}[/red]")
|
|
102
|
+
raise
|
|
103
|
+
except Exception as e:
|
|
104
|
+
console.print(f"[red]Error setting up macOS protocol handler: {e}[/red]")
|
|
105
|
+
raise
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def setup_windows_protocol_handler() -> None:
|
|
109
|
+
"""Set up protocol handler for Windows using registry entries."""
|
|
110
|
+
try:
|
|
111
|
+
import winreg # type: ignore[import-untyped]
|
|
112
|
+
from winreg import HKEY_CURRENT_USER, REG_SZ # type: ignore[import-untyped,attr-defined]
|
|
113
|
+
|
|
114
|
+
hcli_path = get_hcli_executable_path()
|
|
115
|
+
command = f'"{hcli_path}" open "%1"'
|
|
116
|
+
|
|
117
|
+
# Create registry entries for ida:// protocol
|
|
118
|
+
with winreg.CreateKey(HKEY_CURRENT_USER, r"SOFTWARE\Classes\ida") as key: # type: ignore[attr-defined]
|
|
119
|
+
winreg.SetValueEx(key, "", 0, REG_SZ, "URL:IDA Protocol") # type: ignore[attr-defined]
|
|
120
|
+
winreg.SetValueEx(key, "URL Protocol", 0, REG_SZ, "") # type: ignore[attr-defined]
|
|
121
|
+
|
|
122
|
+
with winreg.CreateKey(HKEY_CURRENT_USER, r"SOFTWARE\Classes\ida\DefaultIcon") as key: # type: ignore[attr-defined]
|
|
123
|
+
winreg.SetValueEx(key, "", 0, REG_SZ, f"{hcli_path},1") # type: ignore[attr-defined]
|
|
124
|
+
|
|
125
|
+
with winreg.CreateKey(HKEY_CURRENT_USER, r"SOFTWARE\Classes\ida\shell") as key: # type: ignore[attr-defined]
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
with winreg.CreateKey(HKEY_CURRENT_USER, r"SOFTWARE\Classes\ida\shell\open") as key: # type: ignore[attr-defined]
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
with winreg.CreateKey(HKEY_CURRENT_USER, r"SOFTWARE\Classes\ida\shell\open\command") as key: # type: ignore[attr-defined]
|
|
132
|
+
winreg.SetValueEx(key, "", 0, REG_SZ, command) # type: ignore[attr-defined]
|
|
133
|
+
|
|
134
|
+
console.print("[green]✓[/green] Windows protocol handler registered in registry")
|
|
135
|
+
|
|
136
|
+
except ImportError:
|
|
137
|
+
console.print("[red]winreg module not available (not on Windows?)[/red]")
|
|
138
|
+
raise
|
|
139
|
+
except Exception as e:
|
|
140
|
+
console.print(f"[red]Error setting up Windows protocol handler: {e}[/red]")
|
|
141
|
+
raise
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def setup_linux_protocol_handler() -> None:
|
|
145
|
+
"""Set up protocol handler for Linux using desktop entry and xdg-mime."""
|
|
146
|
+
try:
|
|
147
|
+
hcli_path = get_hcli_executable_path()
|
|
148
|
+
|
|
149
|
+
# Create desktop entry
|
|
150
|
+
desktop_entry_content = f"""[Desktop Entry]
|
|
151
|
+
Name=HCLI URL Handler
|
|
152
|
+
Exec={hcli_path} open %u
|
|
153
|
+
Type=Application
|
|
154
|
+
NoDisplay=true
|
|
155
|
+
MimeType=x-scheme-handler/ida;
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
# Write to applications directory
|
|
159
|
+
applications_dir = Path.home() / ".local" / "share" / "applications"
|
|
160
|
+
applications_dir.mkdir(parents=True, exist_ok=True)
|
|
161
|
+
|
|
162
|
+
desktop_file_path = applications_dir / "hcli-url-handler.desktop"
|
|
163
|
+
desktop_file_path.write_text(desktop_entry_content)
|
|
164
|
+
|
|
165
|
+
# Make executable
|
|
166
|
+
desktop_file_path.chmod(0o755)
|
|
167
|
+
|
|
168
|
+
# Register with xdg-mime
|
|
169
|
+
subprocess.run(["xdg-mime", "default", "hcli-url-handler.desktop", "x-scheme-handler/ida"], check=True)
|
|
170
|
+
|
|
171
|
+
# Update desktop database
|
|
172
|
+
subprocess.run(
|
|
173
|
+
["update-desktop-database", str(applications_dir)], check=False
|
|
174
|
+
) # May fail on some systems but not critical
|
|
175
|
+
|
|
176
|
+
console.print(f"[green]✓[/green] Linux protocol handler installed at {desktop_file_path}")
|
|
177
|
+
|
|
178
|
+
except subprocess.CalledProcessError as e:
|
|
179
|
+
console.print(f"[red]Failed to set up Linux protocol handler: {e}[/red]")
|
|
180
|
+
raise
|
|
181
|
+
except Exception as e:
|
|
182
|
+
console.print(f"[red]Error setting up Linux protocol handler: {e}[/red]")
|
|
183
|
+
raise
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@click.command(name="setup")
|
|
187
|
+
@click.option("--force", is_flag=True, help="Force reinstall even if already configured")
|
|
188
|
+
@async_command
|
|
189
|
+
async def setup(force: bool = False) -> None:
|
|
190
|
+
"""Set up hcli protocol handlers for ida:// URLs.
|
|
191
|
+
|
|
192
|
+
This command registers hcli as the handler for ida:// URLs on your system,
|
|
193
|
+
allowing web browsers and other applications to automatically open IDA-related
|
|
194
|
+
URLs with hcli.
|
|
195
|
+
|
|
196
|
+
The setup process varies by platform:
|
|
197
|
+
- macOS: Creates an AppleScript application and registers it with Launch Services
|
|
198
|
+
- Windows: Adds registry entries for the ida:// protocol
|
|
199
|
+
- Linux: Creates a desktop entry and registers with xdg-mime
|
|
200
|
+
"""
|
|
201
|
+
current_platform = platform.system().lower()
|
|
202
|
+
|
|
203
|
+
console.print(f"[blue]Setting up hcli protocol handlers for {current_platform}...[/blue]")
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
if current_platform == "darwin":
|
|
207
|
+
setup_macos_protocol_handler()
|
|
208
|
+
elif current_platform == "windows":
|
|
209
|
+
setup_windows_protocol_handler()
|
|
210
|
+
elif current_platform == "linux":
|
|
211
|
+
setup_linux_protocol_handler()
|
|
212
|
+
else:
|
|
213
|
+
console.print(f"[red]Unsupported platform: {current_platform}[/red]")
|
|
214
|
+
raise RuntimeError(f"Platform {current_platform} is not supported")
|
|
215
|
+
|
|
216
|
+
console.print("[green]✓ Protocol handler setup complete![/green]")
|
|
217
|
+
console.print("[yellow]You can now click ida:// links and they will open with hcli.[/yellow]")
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
console.print(f"[red]Setup failed: {e}[/red]")
|
|
221
|
+
raise
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import rich_click as click
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.group()
|
|
7
|
+
def source() -> None:
|
|
8
|
+
"""Manage knowledge sources."""
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from .add import add # noqa: E402
|
|
13
|
+
from .list import list_sources # noqa: E402
|
|
14
|
+
from .remove import remove # noqa: E402
|
|
15
|
+
|
|
16
|
+
source.add_command(add)
|
|
17
|
+
source.add_command(remove)
|
|
18
|
+
source.add_command(list_sources, name="list")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import rich_click as click
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from hcli.lib.config import config_store
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command()
|
|
14
|
+
@click.argument("name", type=str)
|
|
15
|
+
@click.argument("path", type=click.Path(exists=True, path_type=Path))
|
|
16
|
+
def add(name: str, path: Path) -> None:
|
|
17
|
+
"""Add a knowledge source.
|
|
18
|
+
|
|
19
|
+
NAME: Logical name for the source
|
|
20
|
+
PATH: Filesystem path to the source
|
|
21
|
+
"""
|
|
22
|
+
# Get existing sources or initialize empty dict
|
|
23
|
+
sources: dict[str, str] = config_store.get_object("ke.sources", {}) or {}
|
|
24
|
+
|
|
25
|
+
if name in sources:
|
|
26
|
+
console.print(f"[yellow]Source '{name}' already exists. Use remove first to replace it.[/yellow]")
|
|
27
|
+
raise click.Abort()
|
|
28
|
+
|
|
29
|
+
# Store the absolute path as string
|
|
30
|
+
sources[name] = str(path.absolute())
|
|
31
|
+
|
|
32
|
+
# Save back to config
|
|
33
|
+
config_store.set_object("ke.sources", sources)
|
|
34
|
+
|
|
35
|
+
console.print(f"[green]Added source '{name}' pointing to '{path.absolute()}'[/green]")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import rich_click as click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
|
|
7
|
+
from hcli.lib.config import config_store
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command()
|
|
13
|
+
def list_sources() -> None:
|
|
14
|
+
"""List all knowledge sources."""
|
|
15
|
+
# Get existing sources
|
|
16
|
+
sources: dict[str, str] = config_store.get_object("ke.sources", {}) or {}
|
|
17
|
+
|
|
18
|
+
if not sources:
|
|
19
|
+
console.print("[yellow]No knowledge sources configured.[/yellow]")
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
# Create table
|
|
23
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
24
|
+
table.add_column("Name", style="cyan", width=20)
|
|
25
|
+
table.add_column("Path", style="white")
|
|
26
|
+
|
|
27
|
+
# Add rows
|
|
28
|
+
for name, path in sources.items():
|
|
29
|
+
table.add_row(name, path)
|
|
30
|
+
|
|
31
|
+
console.print(table)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import rich_click as click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from hcli.lib.config import config_store
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.argument("name", type=str)
|
|
13
|
+
def remove(name: str) -> None:
|
|
14
|
+
"""Remove a knowledge source.
|
|
15
|
+
|
|
16
|
+
NAME: Name of the source to remove
|
|
17
|
+
"""
|
|
18
|
+
# Get existing sources
|
|
19
|
+
sources: dict[str, str] = config_store.get_object("ke.sources", {}) or {}
|
|
20
|
+
|
|
21
|
+
if name not in sources:
|
|
22
|
+
console.print(f"[red]Source '{name}' not found[/red]")
|
|
23
|
+
raise click.Abort()
|
|
24
|
+
|
|
25
|
+
# Remove the source
|
|
26
|
+
del sources[name]
|
|
27
|
+
|
|
28
|
+
# Save back to config
|
|
29
|
+
config_store.set_object("ke.sources", sources)
|
|
30
|
+
|
|
31
|
+
console.print(f"[green]Removed source '{name}'[/green]")
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
import rich_click as click
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from hcli.lib.commands import async_command
|
|
12
|
+
from hcli.lib.config import config_store
|
|
13
|
+
from hcli.lib.ida import IdaVersion, get_default_ida_install_directory, get_ida_binary_path
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.command(name="open", hidden=True)
|
|
19
|
+
@click.argument("url", required=True)
|
|
20
|
+
@async_command
|
|
21
|
+
async def open_url(url: str | None) -> None:
|
|
22
|
+
"""HCLI protocol handler for ida://"""
|
|
23
|
+
|
|
24
|
+
if not url:
|
|
25
|
+
console.print("[red]No URL provided[/red]")
|
|
26
|
+
raise click.Abort()
|
|
27
|
+
|
|
28
|
+
# Parse the URL
|
|
29
|
+
parsed_url = urlparse(url)
|
|
30
|
+
|
|
31
|
+
if parsed_url.scheme != "ida":
|
|
32
|
+
console.print(f"[red]Unsupported URL scheme: {parsed_url.scheme}[/red]")
|
|
33
|
+
raise click.Abort()
|
|
34
|
+
|
|
35
|
+
# Extract source name (hostname) and file path
|
|
36
|
+
source_name = parsed_url.netloc
|
|
37
|
+
file_path = parsed_url.path.lstrip("/") # Remove leading slash
|
|
38
|
+
|
|
39
|
+
if not source_name:
|
|
40
|
+
console.print("[red]No source name provided in URL[/red]")
|
|
41
|
+
raise click.Abort()
|
|
42
|
+
|
|
43
|
+
if not file_path:
|
|
44
|
+
console.print("[red]No file path provided in URL[/red]")
|
|
45
|
+
raise click.Abort()
|
|
46
|
+
|
|
47
|
+
# Get ke sources from config
|
|
48
|
+
sources: dict[str, str] = config_store.get_object("ke.sources", {}) or {}
|
|
49
|
+
|
|
50
|
+
if source_name not in sources:
|
|
51
|
+
console.print(f"[red]Source '{source_name}' not found. Available sources:[/red]")
|
|
52
|
+
if sources:
|
|
53
|
+
for name in sources.keys():
|
|
54
|
+
console.print(f" - {name}")
|
|
55
|
+
else:
|
|
56
|
+
console.print(" No sources configured. Use 'hcli ke source add' to add sources.")
|
|
57
|
+
raise click.Abort()
|
|
58
|
+
|
|
59
|
+
# Resolve full path
|
|
60
|
+
source_path = Path(sources[source_name])
|
|
61
|
+
full_path = source_path / file_path
|
|
62
|
+
|
|
63
|
+
if not full_path.exists():
|
|
64
|
+
console.print(f"[red]File not found: {full_path}[/red]")
|
|
65
|
+
raise click.Abort()
|
|
66
|
+
|
|
67
|
+
# Get IDA binary
|
|
68
|
+
try:
|
|
69
|
+
ida_dir = get_default_ida_install_directory(IdaVersion("IDA Professional", 9, 2))
|
|
70
|
+
ida_bin = get_ida_binary_path(ida_dir)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
console.print(f"[red]IDA Pro not found: {e}[/red]")
|
|
73
|
+
console.print(f"[yellow]URL resolution successful: {url} -> {full_path}[/yellow]")
|
|
74
|
+
raise click.Abort()
|
|
75
|
+
|
|
76
|
+
# Log the URL to a temp file
|
|
77
|
+
log_file = "/tmp/hcli_urls.log"
|
|
78
|
+
timestamp = datetime.now().isoformat()
|
|
79
|
+
|
|
80
|
+
with open(str(log_file), "a", encoding="utf-8") as f:
|
|
81
|
+
f.write(f"{timestamp}: {url} -> {full_path} : {ida_bin}\n")
|
|
82
|
+
|
|
83
|
+
console.print(f"[green]Opening {full_path} with IDA Pro[/green]")
|
|
84
|
+
|
|
85
|
+
# Launch IDA with the resolved file path
|
|
86
|
+
subprocess.Popen([ida_bin, str(full_path)])
|
|
@@ -27,6 +27,12 @@ src/hcli/commands/extension/create.py
|
|
|
27
27
|
src/hcli/commands/extension/list.py
|
|
28
28
|
src/hcli/commands/ida/__init__.py
|
|
29
29
|
src/hcli/commands/ida/install.py
|
|
30
|
+
src/hcli/commands/ke/__init__.py
|
|
31
|
+
src/hcli/commands/ke/setup.py
|
|
32
|
+
src/hcli/commands/ke/source/__init__.py
|
|
33
|
+
src/hcli/commands/ke/source/add.py
|
|
34
|
+
src/hcli/commands/ke/source/list.py
|
|
35
|
+
src/hcli/commands/ke/source/remove.py
|
|
30
36
|
src/hcli/commands/license/__init__.py
|
|
31
37
|
src/hcli/commands/license/common.py
|
|
32
38
|
src/hcli/commands/license/get.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.8.3-dev.2"
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import subprocess
|
|
4
|
-
from datetime import datetime
|
|
5
|
-
|
|
6
|
-
import rich_click as click
|
|
7
|
-
|
|
8
|
-
from hcli.lib.commands import async_command
|
|
9
|
-
from hcli.lib.ida import IdaVersion, get_default_ida_install_directory, get_ida_binary_path
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@click.command(name="open", hidden=True)
|
|
13
|
-
@click.argument("url", required=True)
|
|
14
|
-
@async_command
|
|
15
|
-
async def open_url(url: str | None) -> None:
|
|
16
|
-
"""HCLI protocol handler for ida://"""
|
|
17
|
-
|
|
18
|
-
ida_dir = get_default_ida_install_directory(IdaVersion("IDA Professional", 9, 2))
|
|
19
|
-
ida_bin = get_ida_binary_path(ida_dir)
|
|
20
|
-
|
|
21
|
-
# Log the URL to a temp file
|
|
22
|
-
log_file = "/tmp/hcli_urls.log"
|
|
23
|
-
timestamp = datetime.now().isoformat()
|
|
24
|
-
|
|
25
|
-
with open(str(log_file), "a", encoding="utf-8") as f:
|
|
26
|
-
f.write(f"{timestamp}: {url} : {ida_bin}\n")
|
|
27
|
-
|
|
28
|
-
subprocess.Popen(["open", "-a", ida_bin])
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|