sharkit 0.2.1.dev4__tar.gz → 1.0.0.dev1__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.
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/PKG-INFO +1 -1
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/pyproject.toml +1 -1
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/__init__.py +1 -1
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/__main__.py +2 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/app.py +4 -2
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/commands/base.py +3 -3
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/commands/builtins.py +90 -82
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/config/manager.py +7 -9
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/config/paths.py +8 -1
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/console/completion.py +42 -8
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/console/prompt.py +14 -2
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/console/repl.py +2 -4
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/exceptions.py +3 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/history/manager.py +20 -5
- sharkit-1.0.0.dev1/src/sharkit/network/client.py +95 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/output/renderer.py +33 -12
- sharkit-1.0.0.dev1/src/sharkit/output/theme.py +38 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/tools/base.py +16 -4
- sharkit-1.0.0.dev1/src/sharkit/tools/humint/__init__.py +0 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/humint/truthcheck.py +195 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/humint/verifact.py +188 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/tools/loader.py +5 -1
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/tools/manager.py +22 -8
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/ahmia_search.py +74 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/email_reputation.py +87 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/hibp_passwords.py +83 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/humint/mailaccess.py +199 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/humint/pyahmia.py +187 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/humint/scraperx.py +187 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/humint/scweet.py +194 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/humint/socialscan.py +198 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/leak_check.py +85 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/psbdmp.py +75 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/dns/dns_lookup.py +180 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/domain/cve_search.py +155 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/domain/otx_query.py +126 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/domain/urlhaus_check.py +127 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/email/email_security.py +131 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/email/holehe.py +210 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/email/theharvester.py +216 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/tools/osint/recon/hybrid/blackbird.py +12 -9
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/network/bgpv6.py +94 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/network/http_headers.py +108 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/network/ip_geolocate.py +86 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/network/ip_info.py +76 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/network/mylnikov.py +79 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/network/port_scanner.py +140 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/network/robtex.py +89 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/network/shodan_internetdb.py +80 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/network/ssl_check.py +116 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/network/whois_lookup.py +141 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/phone/phoneinfoga.py +210 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/tools/osint/recon/username/maigret.py +12 -9
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/tools/osint/recon/username/sherlock.py +12 -9
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/username/social_analyzer.py +210 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/username/socid_extractor.py +210 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/web/robots_check.py +138 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/recon/web/wayback_check.py +122 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/util/crypto/mempool.py +85 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/util/hash/circl_hashlookup.py +77 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/util/hash/hash_decrypt.py +90 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/util/hash/hash_lookup.py +71 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/util/image/exif_reader.py +157 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/util/text/base64_tool.py +79 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/util/text/text_entropy.py +81 -0
- sharkit-1.0.0.dev1/src/sharkit/tools/osint/xposed_or_not.py +80 -0
- sharkit-0.2.1.dev4/src/sharkit/network/client.py +0 -81
- sharkit-0.2.1.dev4/src/sharkit/output/theme.py +0 -18
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/.gitignore +0 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/LICENSE +0 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/README.md +0 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/commands/__init__.py +0 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/commands/registry.py +0 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/config/__init__.py +0 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/console/__init__.py +0 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/history/__init__.py +0 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/network/__init__.py +0 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/output/__init__.py +0 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/tools/__init__.py +0 -0
- {sharkit-0.2.1.dev4 → sharkit-1.0.0.dev1}/src/sharkit/tools/registry.py +0 -0
|
@@ -33,8 +33,9 @@ NON_LINUX_MESSAGE = (
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def main() -> int:
|
|
36
|
-
sys.stdout.
|
|
37
|
-
|
|
36
|
+
if sys.stdout.isatty():
|
|
37
|
+
sys.stdout.write("\033[2J\033[3J\033[H")
|
|
38
|
+
sys.stdout.flush()
|
|
38
39
|
renderer = Renderer()
|
|
39
40
|
|
|
40
41
|
if sys.platform == "win32":
|
|
@@ -69,6 +70,7 @@ def main() -> int:
|
|
|
69
70
|
history_manager=history_manager,
|
|
70
71
|
renderer=renderer,
|
|
71
72
|
)
|
|
73
|
+
console._session_data["http_client"] = http_client
|
|
72
74
|
|
|
73
75
|
try:
|
|
74
76
|
console.run()
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any, ClassVar
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@dataclass
|
|
@@ -15,10 +15,10 @@ class CommandContext:
|
|
|
15
15
|
|
|
16
16
|
class Command(ABC):
|
|
17
17
|
name: str
|
|
18
|
-
aliases: list[str] = []
|
|
18
|
+
aliases: ClassVar[list[str]] = []
|
|
19
19
|
description: str = ""
|
|
20
20
|
usage: str = ""
|
|
21
|
-
options: list[tuple[str, str]] = []
|
|
21
|
+
options: ClassVar[list[tuple[str, str]]] = []
|
|
22
22
|
notes: str = ""
|
|
23
23
|
|
|
24
24
|
@abstractmethod
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
import sys
|
|
5
|
+
from collections.abc import Callable
|
|
4
6
|
from pathlib import Path
|
|
5
7
|
from typing import Any
|
|
6
8
|
|
|
7
9
|
from sharkit import __version__
|
|
8
10
|
from sharkit.commands.base import Command, CommandContext
|
|
11
|
+
from sharkit.exceptions import CommandNotFoundError
|
|
9
12
|
from sharkit.output.theme import BLUE, BOLD, GREEN, PINK, RED, RESET
|
|
10
13
|
from sharkit.tools.base import Tool
|
|
11
14
|
|
|
@@ -22,6 +25,19 @@ def _normalize_bool(value: str) -> str:
|
|
|
22
25
|
return value
|
|
23
26
|
|
|
24
27
|
|
|
28
|
+
def _make_progress_callback(
|
|
29
|
+
renderer: Any, tag: str, color: str, first_progress: list[bool]
|
|
30
|
+
) -> Callable[[str], None]:
|
|
31
|
+
def on_progress(msg: str) -> None:
|
|
32
|
+
if renderer is not None:
|
|
33
|
+
if first_progress[0]:
|
|
34
|
+
renderer.gutter(tag, msg, color, is_first=True)
|
|
35
|
+
first_progress[0] = False
|
|
36
|
+
else:
|
|
37
|
+
renderer.gutter(tag, msg, color, is_first=False)
|
|
38
|
+
return on_progress
|
|
39
|
+
|
|
40
|
+
|
|
25
41
|
def _get_tool_instance(context: CommandContext) -> Tool | None:
|
|
26
42
|
tool_name = context.current_tool
|
|
27
43
|
if tool_name is None:
|
|
@@ -70,7 +86,7 @@ class HelpCommand(Command):
|
|
|
70
86
|
return "No command registry available."
|
|
71
87
|
try:
|
|
72
88
|
cmd_class = registry.get_command(name)
|
|
73
|
-
except
|
|
89
|
+
except (CommandNotFoundError, KeyError):
|
|
74
90
|
return f'Command "{name}" not found.'
|
|
75
91
|
if cmd_class is None:
|
|
76
92
|
return f'Command "{name}" not found.'
|
|
@@ -173,7 +189,7 @@ class UseCommand(Command):
|
|
|
173
189
|
def _log(self, context: CommandContext, message: str) -> None:
|
|
174
190
|
renderer = context.session.get("renderer")
|
|
175
191
|
if renderer is not None:
|
|
176
|
-
renderer.
|
|
192
|
+
renderer.dash_line("use", message)
|
|
177
193
|
|
|
178
194
|
def validate_args(self, args: list[str]) -> tuple[bool, str | None]:
|
|
179
195
|
if not args:
|
|
@@ -194,15 +210,9 @@ class UseCommand(Command):
|
|
|
194
210
|
return None
|
|
195
211
|
renderer = context.session.get("renderer")
|
|
196
212
|
first_progress = [True]
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
tag = f"install {display}"
|
|
201
|
-
if first_progress[0]:
|
|
202
|
-
renderer.gutter(tag, msg, GREEN, is_first=True)
|
|
203
|
-
first_progress[0] = False
|
|
204
|
-
else:
|
|
205
|
-
renderer.gutter(tag, msg, GREEN, is_first=False)
|
|
213
|
+
on_progress = _make_progress_callback(
|
|
214
|
+
renderer, f"install {display}", GREEN, first_progress
|
|
215
|
+
)
|
|
206
216
|
|
|
207
217
|
ok, message = tool_manager.install(name, spec, on_progress=on_progress)
|
|
208
218
|
if not ok:
|
|
@@ -268,7 +278,7 @@ class InfoCommand(Command):
|
|
|
268
278
|
f"safety {metadata.safety}",
|
|
269
279
|
]
|
|
270
280
|
return "\n".join(lines)
|
|
271
|
-
except
|
|
281
|
+
except (CommandNotFoundError, AttributeError):
|
|
272
282
|
return f"Tool: {context.current_tool}"
|
|
273
283
|
|
|
274
284
|
|
|
@@ -276,20 +286,22 @@ class ShowCommand(Command):
|
|
|
276
286
|
name = "show"
|
|
277
287
|
aliases = []
|
|
278
288
|
description = "Show tools or tool options"
|
|
279
|
-
usage = "show <
|
|
289
|
+
usage = "show <all|osint|humint|options> [tool]"
|
|
280
290
|
|
|
281
291
|
def execute(self, context: CommandContext, args: list[str]) -> str | None:
|
|
282
292
|
if not args:
|
|
283
293
|
if context.current_tool is not None:
|
|
284
294
|
return self._show_options(context, None)
|
|
285
|
-
return self._show_tools(context)
|
|
295
|
+
return self._show_tools(context, None)
|
|
286
296
|
if args[0] == "options":
|
|
287
297
|
return self._show_options(context, args[1] if len(args) > 1 else None)
|
|
288
|
-
if args[0] == "
|
|
289
|
-
return self._show_tools(context)
|
|
298
|
+
if args[0] == "all":
|
|
299
|
+
return self._show_tools(context, None)
|
|
300
|
+
if args[0] in ("osint", "humint"):
|
|
301
|
+
return self._show_tools(context, args[0])
|
|
290
302
|
return f"Unknown show subcommand: {args[0]}\nUsage: {self.usage}"
|
|
291
303
|
|
|
292
|
-
def _show_tools(self, context: CommandContext) -> str | None:
|
|
304
|
+
def _show_tools(self, context: CommandContext, category: str | None) -> str | None:
|
|
293
305
|
tool_registry = context.session.get("tool_registry")
|
|
294
306
|
tool_manager = context.session.get("tool_manager")
|
|
295
307
|
renderer = context.session.get("renderer")
|
|
@@ -298,17 +310,32 @@ class ShowCommand(Command):
|
|
|
298
310
|
all_tools = tool_registry.get_all_tools()
|
|
299
311
|
if not all_tools:
|
|
300
312
|
return "No tools available."
|
|
301
|
-
|
|
313
|
+
|
|
314
|
+
categories: dict[str, list[tuple[str, type]]] = {}
|
|
302
315
|
for tool_path, tool_cls in all_tools.items():
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
316
|
+
meta = tool_cls.metadata
|
|
317
|
+
cat = meta.category.split(".")[0] if meta.category else "other"
|
|
318
|
+
if category and cat != category:
|
|
319
|
+
continue
|
|
320
|
+
categories.setdefault(cat, []).append((tool_path, tool_cls))
|
|
321
|
+
|
|
322
|
+
if not categories:
|
|
323
|
+
return f'No tools found for category "{category}".'
|
|
324
|
+
|
|
325
|
+
title = f"show > {category or 'all'}"
|
|
326
|
+
for cat_name in sorted(categories):
|
|
327
|
+
tools = sorted(categories[cat_name], key=lambda x: x[0])
|
|
328
|
+
rows = []
|
|
329
|
+
for tool_path, tool_cls in tools:
|
|
330
|
+
meta = tool_cls.metadata
|
|
331
|
+
if meta.install is None or (
|
|
332
|
+
tool_manager is not None and tool_manager.is_installed(meta.name)
|
|
333
|
+
):
|
|
334
|
+
installed = f"{BOLD}{PINK}Yes{RESET}"
|
|
335
|
+
else:
|
|
336
|
+
installed = ""
|
|
337
|
+
rows.append([tool_path, installed, meta.description])
|
|
338
|
+
renderer.table(f"{title} > {cat_name}", ["Tool", "Installed", "Description"], rows)
|
|
312
339
|
return None
|
|
313
340
|
|
|
314
341
|
def _show_options(self, context: CommandContext, tool_arg: str | None) -> str | None:
|
|
@@ -319,12 +346,12 @@ class ShowCommand(Command):
|
|
|
319
346
|
if tool_arg is not None:
|
|
320
347
|
tool_cls = tool_registry.get_tool(tool_arg)
|
|
321
348
|
if tool_cls is None:
|
|
322
|
-
return f'Tool "{tool_arg}" not found.\nTry: show
|
|
349
|
+
return f'Tool "{tool_arg}" not found.\nTry: show all'
|
|
323
350
|
instance = tool_cls()
|
|
324
351
|
title_tool = tool_cls.metadata.name
|
|
325
352
|
else:
|
|
326
353
|
if context.current_tool is None:
|
|
327
|
-
return "No tool selected. Use 'show
|
|
354
|
+
return "No tool selected. Use 'show all' or 'use <tool>' first."
|
|
328
355
|
tool_cls = tool_registry.get_tool(context.current_tool)
|
|
329
356
|
if tool_cls is None:
|
|
330
357
|
return "Current tool not found."
|
|
@@ -338,7 +365,11 @@ class ShowCommand(Command):
|
|
|
338
365
|
return "This tool has no options."
|
|
339
366
|
rows = []
|
|
340
367
|
for opt in options.values():
|
|
341
|
-
value =
|
|
368
|
+
value = (
|
|
369
|
+
opt.value
|
|
370
|
+
if opt.value is not None
|
|
371
|
+
else (opt.default if opt.default is not None else "not set")
|
|
372
|
+
)
|
|
342
373
|
required = " (required)" if opt.required else ""
|
|
343
374
|
rows.append([opt.name, value, f"{opt.description}{required}"])
|
|
344
375
|
renderer.table(
|
|
@@ -347,7 +378,7 @@ class ShowCommand(Command):
|
|
|
347
378
|
rows,
|
|
348
379
|
)
|
|
349
380
|
return None
|
|
350
|
-
except
|
|
381
|
+
except (CommandNotFoundError, KeyError, AttributeError):
|
|
351
382
|
return "Could not retrieve options for tool."
|
|
352
383
|
|
|
353
384
|
|
|
@@ -374,6 +405,8 @@ class SetCommand(Command):
|
|
|
374
405
|
return f'Option "{key}" not found.\nAvailable options: {available}'
|
|
375
406
|
option = options[key]
|
|
376
407
|
raw = " ".join(args[1:])
|
|
408
|
+
if not raw and option.required:
|
|
409
|
+
return f'Option "{key}" requires a value.'
|
|
377
410
|
if option.type == "bool":
|
|
378
411
|
value = _normalize_bool(raw)
|
|
379
412
|
elif option.choices:
|
|
@@ -394,7 +427,7 @@ class SetCommand(Command):
|
|
|
394
427
|
display = tool_registry.format_display(
|
|
395
428
|
tool_registry.get_tool_path(context.current_tool),
|
|
396
429
|
)
|
|
397
|
-
renderer.
|
|
430
|
+
renderer.dash_line(f"set {display}", f"{key} = {value}")
|
|
398
431
|
return None
|
|
399
432
|
return f"Set {key} = {value}"
|
|
400
433
|
except Exception as e:
|
|
@@ -452,7 +485,10 @@ class RunCommand(Command):
|
|
|
452
485
|
return "Tool registry not available."
|
|
453
486
|
try:
|
|
454
487
|
options = tool_instance.get_options()
|
|
455
|
-
option_values = {
|
|
488
|
+
option_values = {
|
|
489
|
+
k: v.value if v.value is not None else (v.default if v.default is not None else "")
|
|
490
|
+
for k, v in options.items()
|
|
491
|
+
}
|
|
456
492
|
config_dir = context.session.get("config_dir") or Path("/tmp")
|
|
457
493
|
from sharkit.tools.base import ExecutionContext
|
|
458
494
|
|
|
@@ -475,7 +511,9 @@ class RunCommand(Command):
|
|
|
475
511
|
if result.success:
|
|
476
512
|
if result.data:
|
|
477
513
|
if renderer is not None:
|
|
478
|
-
|
|
514
|
+
tool_name = tool_instance.metadata.name
|
|
515
|
+
tool_color = tool_instance.metadata.color or "#888888"
|
|
516
|
+
renderer.tool_output(result.data, tool_name, tool_color)
|
|
479
517
|
return None
|
|
480
518
|
lines = [f"{key}: {value}" for key, value in result.data.items()]
|
|
481
519
|
return "\n".join(lines) if lines else "Tool executed successfully."
|
|
@@ -486,7 +524,8 @@ class RunCommand(Command):
|
|
|
486
524
|
renderer.error(f"Tool execution failed: {result.error}")
|
|
487
525
|
return None
|
|
488
526
|
return f"Tool execution failed: {result.error}"
|
|
489
|
-
except
|
|
527
|
+
except (RuntimeError, ValueError, OSError) as e:
|
|
528
|
+
logging.getLogger(__name__).exception("Tool execution error")
|
|
490
529
|
return f"Tool execution error: {e}"
|
|
491
530
|
|
|
492
531
|
|
|
@@ -500,7 +539,7 @@ class SearchCommand(Command):
|
|
|
500
539
|
valid, error = self.validate_args(args)
|
|
501
540
|
if not valid:
|
|
502
541
|
return error
|
|
503
|
-
query = args
|
|
542
|
+
query = " ".join(args).lower()
|
|
504
543
|
tool_registry = context.session.get("tool_registry")
|
|
505
544
|
if tool_registry is None:
|
|
506
545
|
return "Tool registry not available."
|
|
@@ -648,15 +687,9 @@ class InstallCommand(Command):
|
|
|
648
687
|
return None
|
|
649
688
|
renderer = context.session.get("renderer")
|
|
650
689
|
first_progress = [True]
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
tag = f"install {display}"
|
|
655
|
-
if first_progress[0]:
|
|
656
|
-
renderer.gutter(tag, msg, GREEN, is_first=True)
|
|
657
|
-
first_progress[0] = False
|
|
658
|
-
else:
|
|
659
|
-
renderer.gutter(tag, msg, GREEN, is_first=False)
|
|
690
|
+
on_progress = _make_progress_callback(
|
|
691
|
+
renderer, f"install {display}", GREEN, first_progress
|
|
692
|
+
)
|
|
660
693
|
|
|
661
694
|
install_spec = tool_cls.metadata.install
|
|
662
695
|
ok, message = tool_manager.install(
|
|
@@ -675,9 +708,8 @@ class InstallCommand(Command):
|
|
|
675
708
|
renderer.log_line(tag, msg, color=GREEN)
|
|
676
709
|
else:
|
|
677
710
|
lines = msg.splitlines() or [""]
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
print(f"{GREEN}{BOLD}│{RESET} {line}")
|
|
711
|
+
for line in lines:
|
|
712
|
+
renderer.gutter(tag, line, GREEN, is_first=False)
|
|
681
713
|
return None
|
|
682
714
|
return str(message)
|
|
683
715
|
|
|
@@ -707,21 +739,12 @@ class UpgradeCommand(Command):
|
|
|
707
739
|
tool_name = tool_cls.metadata.name
|
|
708
740
|
display = tool_registry.format_display(tool_registry.get_tool_path(tool_name))
|
|
709
741
|
if not tool_manager.is_installed(tool_name):
|
|
710
|
-
renderer = context.session.get("renderer")
|
|
711
|
-
if renderer is not None:
|
|
712
|
-
renderer.log_line(f"upgrade {display}", "not installed (skipped)", color=BLUE)
|
|
713
742
|
return None
|
|
714
743
|
renderer = context.session.get("renderer")
|
|
715
744
|
first_progress = [True]
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
tag = f"upgrade {display}"
|
|
720
|
-
if first_progress[0]:
|
|
721
|
-
renderer.gutter(tag, msg, BLUE, is_first=True)
|
|
722
|
-
first_progress[0] = False
|
|
723
|
-
else:
|
|
724
|
-
renderer.gutter(tag, msg, BLUE, is_first=False)
|
|
745
|
+
on_progress = _make_progress_callback(
|
|
746
|
+
renderer, f"upgrade {display}", BLUE, first_progress
|
|
747
|
+
)
|
|
725
748
|
|
|
726
749
|
upgrade_spec = tool_cls.metadata.install
|
|
727
750
|
ok, message = tool_manager.update(
|
|
@@ -740,9 +763,8 @@ class UpgradeCommand(Command):
|
|
|
740
763
|
renderer.log_line(tag, msg, color=BLUE)
|
|
741
764
|
else:
|
|
742
765
|
lines = msg.splitlines() or [""]
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
print(f"{BLUE}{BOLD}│{RESET} {line}")
|
|
766
|
+
for line in lines:
|
|
767
|
+
renderer.gutter(tag, line, BLUE, is_first=False)
|
|
746
768
|
return None
|
|
747
769
|
return str(message)
|
|
748
770
|
|
|
@@ -770,25 +792,12 @@ class UpgradeCommand(Command):
|
|
|
770
792
|
name = meta.name
|
|
771
793
|
display = tool_registry.format_display(tool_registry.get_tool_path(name))
|
|
772
794
|
if not tool_manager.is_installed(name):
|
|
773
|
-
renderer = context.session.get("renderer")
|
|
774
|
-
if renderer is not None:
|
|
775
|
-
renderer.log_line(f"upgrade {display}", "not installed (skipped)", color=BLUE)
|
|
776
795
|
continue
|
|
777
796
|
renderer = context.session.get("renderer")
|
|
778
797
|
first_progress = [True]
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
_first: list[bool] = first_progress,
|
|
783
|
-
_tag: str = f"upgrade {display}",
|
|
784
|
-
_renderer: Any = renderer,
|
|
785
|
-
) -> None:
|
|
786
|
-
if _renderer is not None:
|
|
787
|
-
if _first[0]:
|
|
788
|
-
_renderer.gutter(_tag, msg, BLUE, is_first=True)
|
|
789
|
-
_first[0] = False
|
|
790
|
-
else:
|
|
791
|
-
_renderer.gutter(_tag, msg, BLUE, is_first=False)
|
|
798
|
+
on_progress = _make_progress_callback(
|
|
799
|
+
renderer, f"upgrade {display}", BLUE, first_progress
|
|
800
|
+
)
|
|
792
801
|
|
|
793
802
|
ok, message = tool_manager.update(name, spec, on_progress=on_progress)
|
|
794
803
|
if renderer is not None:
|
|
@@ -804,9 +813,8 @@ class UpgradeCommand(Command):
|
|
|
804
813
|
renderer.log_line(tag, msg, color=BLUE)
|
|
805
814
|
else:
|
|
806
815
|
lines = msg.splitlines() or [""]
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
print(f"{BLUE}{BOLD}│{RESET} {line}")
|
|
816
|
+
for line in lines:
|
|
817
|
+
renderer.gutter(tag, line, BLUE, is_first=False)
|
|
810
818
|
return None
|
|
811
819
|
|
|
812
820
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
from sharkit.config.paths import (
|
|
@@ -34,7 +35,7 @@ class ConfigManager:
|
|
|
34
35
|
def _load(self) -> None:
|
|
35
36
|
try:
|
|
36
37
|
content = self._config_file.read_text(encoding="utf-8")
|
|
37
|
-
except OSError as exc:
|
|
38
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
38
39
|
raise ConfigurationError(f"Failed to read config file: {exc}") from exc
|
|
39
40
|
|
|
40
41
|
for line_number, raw_line in enumerate(content.splitlines(), start=1):
|
|
@@ -49,16 +50,13 @@ class ConfigManager:
|
|
|
49
50
|
self._config[key.strip()] = value.strip()
|
|
50
51
|
|
|
51
52
|
def _save(self) -> None:
|
|
52
|
-
lines = []
|
|
53
|
-
for key, value in sorted(self._config.items()):
|
|
54
|
-
lines.append(f"{key}={value}")
|
|
55
|
-
content = "\n".join(lines)
|
|
56
|
-
if content:
|
|
57
|
-
content += "\n"
|
|
58
53
|
try:
|
|
59
|
-
self._config_file.
|
|
54
|
+
tmp = self._config_file.with_suffix(".tmp")
|
|
55
|
+
lines = [f"{k}={v}" for k, v in sorted(self._config.items())]
|
|
56
|
+
tmp.write_text("\n".join(lines) + "\n" if lines else "", encoding="utf-8")
|
|
57
|
+
os.replace(tmp, self._config_file)
|
|
60
58
|
except OSError as exc:
|
|
61
|
-
raise ConfigurationError(f"Failed to
|
|
59
|
+
raise ConfigurationError(f"Failed to save config: {exc}") from exc
|
|
62
60
|
|
|
63
61
|
def get(self, key: str, default: str | None = None) -> str | None:
|
|
64
62
|
return self._config.get(key, default)
|
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
+
from sharkit.exceptions import ConfigurationError
|
|
6
|
+
|
|
5
7
|
_BASE_DIR_NAME = "sharkit"
|
|
6
8
|
|
|
7
9
|
|
|
@@ -11,7 +13,12 @@ def _get_xdg_config_home() -> Path:
|
|
|
11
13
|
xdg = os.environ.get("XDG_CONFIG_HOME")
|
|
12
14
|
if xdg:
|
|
13
15
|
return Path(xdg)
|
|
14
|
-
|
|
16
|
+
try:
|
|
17
|
+
return Path.home() / ".config"
|
|
18
|
+
except RuntimeError:
|
|
19
|
+
raise ConfigurationError(
|
|
20
|
+
"Cannot determine home directory. Set XDG_CONFIG_HOME manually."
|
|
21
|
+
) from None
|
|
15
22
|
|
|
16
23
|
|
|
17
24
|
def get_config_dir() -> Path:
|
|
@@ -1,10 +1,30 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from sharkit.commands.registry import CommandRegistry
|
|
4
|
+
from sharkit.tools.base import OptionDefinition
|
|
4
5
|
from sharkit.tools.registry import ToolRegistry
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class CompletionEngine:
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
self._cached_tool: str | None = None
|
|
11
|
+
self._cached_options: dict[str, OptionDefinition] | None = None
|
|
12
|
+
|
|
13
|
+
def _load_options(
|
|
14
|
+
self, tool_registry: ToolRegistry, current_tool: str
|
|
15
|
+
) -> dict[str, OptionDefinition]:
|
|
16
|
+
if current_tool == self._cached_tool and self._cached_options is not None:
|
|
17
|
+
return self._cached_options
|
|
18
|
+
tool_cls = tool_registry.get_tool(current_tool)
|
|
19
|
+
if tool_cls is None:
|
|
20
|
+
self._cached_tool = None
|
|
21
|
+
self._cached_options = None
|
|
22
|
+
return {}
|
|
23
|
+
instance = tool_cls()
|
|
24
|
+
self._cached_options = instance.get_options()
|
|
25
|
+
self._cached_tool = current_tool
|
|
26
|
+
return self._cached_options
|
|
27
|
+
|
|
8
28
|
def get_completions(
|
|
9
29
|
self,
|
|
10
30
|
text: str,
|
|
@@ -22,22 +42,36 @@ class CompletionEngine:
|
|
|
22
42
|
|
|
23
43
|
if text_lower.startswith("show "):
|
|
24
44
|
prefix = text_lower[5:]
|
|
25
|
-
return sorted(s for s in ("
|
|
45
|
+
return sorted(s for s in ("all", "osint", "humint", "options") if s.startswith(prefix))
|
|
26
46
|
|
|
27
47
|
if text_lower.startswith("set ") or text_lower.startswith("unset "):
|
|
28
48
|
if current_tool is None:
|
|
29
49
|
return []
|
|
50
|
+
is_set = text_lower.startswith("set ")
|
|
51
|
+
body = text[4:] if is_set else text[6:]
|
|
52
|
+
parts = body.split(None, 1)
|
|
53
|
+
|
|
30
54
|
try:
|
|
31
|
-
|
|
32
|
-
if tool_cls is None:
|
|
33
|
-
return []
|
|
34
|
-
instance = tool_cls()
|
|
35
|
-
options = instance.get_options()
|
|
36
|
-
prefix = text_lower[4:] if text_lower.startswith("set ") else text_lower[6:]
|
|
37
|
-
return sorted(k for k in options if k.lower().startswith(prefix))
|
|
55
|
+
options = self._load_options(tool_registry, current_tool)
|
|
38
56
|
except Exception:
|
|
39
57
|
return []
|
|
40
58
|
|
|
59
|
+
if len(parts) < 2:
|
|
60
|
+
prefix = parts[0] if parts else ""
|
|
61
|
+
exact = options.get(prefix)
|
|
62
|
+
if exact and exact.choices:
|
|
63
|
+
return list(exact.choices)
|
|
64
|
+
elif exact and exact.type == "bool":
|
|
65
|
+
return ["true", "false"]
|
|
66
|
+
return sorted(k for k in options if k.lower().startswith(prefix))
|
|
67
|
+
|
|
68
|
+
option_name = parts[0]
|
|
69
|
+
value_prefix = parts[1]
|
|
70
|
+
opt = options.get(option_name)
|
|
71
|
+
if opt is None or not opt.choices:
|
|
72
|
+
return []
|
|
73
|
+
return sorted(c for c in opt.choices if c.lower().startswith(value_prefix.lower()))
|
|
74
|
+
|
|
41
75
|
if text_lower.startswith("install "):
|
|
42
76
|
prefix = text_lower[8:]
|
|
43
77
|
return sorted(
|
|
@@ -16,7 +16,19 @@ class PromptRenderer:
|
|
|
16
16
|
formatted = tool_name
|
|
17
17
|
return f"shark {formatted} > "
|
|
18
18
|
|
|
19
|
-
def render(self, prompt: str) -> list[tuple[str, str]]:
|
|
19
|
+
def render(self, prompt: str, tool_name: str | None = None) -> list[tuple[str, str]]:
|
|
20
|
+
if tool_name is not None and "/" in tool_name:
|
|
21
|
+
cat, rest = tool_name.split("/", 1)
|
|
22
|
+
before = "shark "
|
|
23
|
+
after = " > "
|
|
24
|
+
return [
|
|
25
|
+
(PROMPT_STYLE, before),
|
|
26
|
+
(MODULE_STYLE, cat),
|
|
27
|
+
("", "("),
|
|
28
|
+
(MODULE_STYLE, rest),
|
|
29
|
+
("", ")"),
|
|
30
|
+
("", after),
|
|
31
|
+
]
|
|
20
32
|
if "(" in prompt and ")" in prompt:
|
|
21
33
|
before, rest = prompt.split("(", 1)
|
|
22
34
|
tool_path, after = rest.split(")", 1)
|
|
@@ -25,6 +37,6 @@ class PromptRenderer:
|
|
|
25
37
|
("", "("),
|
|
26
38
|
(MODULE_STYLE, tool_path),
|
|
27
39
|
("", ")"),
|
|
28
|
-
(
|
|
40
|
+
(PROMPT_STYLE, after),
|
|
29
41
|
]
|
|
30
42
|
return [(PROMPT_STYLE, prompt)]
|
|
@@ -31,7 +31,7 @@ class _SharkitHistory(History):
|
|
|
31
31
|
super().__init__()
|
|
32
32
|
|
|
33
33
|
def load_history_strings(self) -> list[str]:
|
|
34
|
-
return self._manager.get_history()
|
|
34
|
+
return list(reversed(self._manager.get_history()))
|
|
35
35
|
|
|
36
36
|
def store_string(self, string: str) -> None:
|
|
37
37
|
self._manager.add_command(string)
|
|
@@ -108,7 +108,7 @@ class Console:
|
|
|
108
108
|
prompt = self._prompt_renderer.tool_prompt(self._current_tool)
|
|
109
109
|
else:
|
|
110
110
|
prompt = self._prompt_renderer.default_prompt()
|
|
111
|
-
return self._prompt_renderer.render(prompt)
|
|
111
|
+
return self._prompt_renderer.render(prompt, tool_name=self._current_tool)
|
|
112
112
|
|
|
113
113
|
def _init_session(self) -> None:
|
|
114
114
|
self._prompt_renderer = PromptRenderer()
|
|
@@ -144,8 +144,6 @@ class Console:
|
|
|
144
144
|
if not stripped:
|
|
145
145
|
continue
|
|
146
146
|
|
|
147
|
-
if not line[:1].isspace():
|
|
148
|
-
self._history_manager.add_command(stripped)
|
|
149
147
|
self._session_data["history"] = self._history_manager.get_history()
|
|
150
148
|
|
|
151
149
|
if not self.process_command(stripped):
|