Skip to content

Commit 598d213

Browse files
author
r0BIT
committed
feat: Rich plain output with auto-output, ASCII symbols, and offline fixes
1 parent ee6bc1e commit 598d213

File tree

8 files changed

+550
-26
lines changed

8 files changed

+550
-26
lines changed

taskhound/cli.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from .output.bloodhound import upload_opengraph_to_bloodhound
2424
from .output.printer import print_results
2525
from .output.summary import print_decrypted_credentials, print_summary_table
26-
from .output.writer import write_csv, write_json, write_plain
26+
from .output.writer import write_csv, write_json, write_rich_plain
2727
from .parsers.highvalue import HighValueLoader
2828
from .utils.cache_manager import init_cache
2929
from .utils.console import print_banner
@@ -250,8 +250,6 @@ def main():
250250
for result in results:
251251
if result.lines:
252252
print_results(result.lines)
253-
if args.plain and result.lines:
254-
write_plain(args.plain, result.target, result.lines)
255253

256254
# Summary already printed by async engine's Rich progress bar
257255

@@ -270,8 +268,6 @@ def main():
270268
elif isinstance(laps_result, LAPSFailure):
271269
laps_failures.append(laps_result)
272270
print_results(lines)
273-
if args.plain and lines:
274-
write_plain(args.plain, tgt, lines)
275271

276272
# Exports
277273
# Auto-generate JSON if OpenGraph is enabled and no explicit JSON output was specified
@@ -298,6 +294,16 @@ def main():
298294
write_json(args.json, all_rows)
299295
if args.csv:
300296
write_csv(args.csv, all_rows)
297+
298+
# Auto-enable plain output in concise mode (default) to ./output
299+
# In verbose/debug mode, user sees details on screen so auto-output is optional
300+
is_concise = not (args.verbose or args.debug)
301+
if args.plain:
302+
write_rich_plain(args.plain, all_rows)
303+
elif is_concise and all_rows:
304+
# Auto-write to ./output in concise mode
305+
write_rich_plain("./output", all_rows)
306+
301307
if args.opengraph:
302308
generate_opengraph_files(args.opengraph, all_rows)
303309

taskhound/engine/async_runner.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,14 @@ def _process_single(
235235
if self._progress and self._task_id is not None:
236236
# Build status text
237237
if result.skipped:
238-
status_text = f"[yellow][/] {target} [dim](skipped)[/]"
238+
status_text = f"[yellow][~][/] {target} [dim](skipped)[/]"
239239
elif result.success:
240240
task_count = len([r for r in result.rows if r.type not in ("FAILURE", None)])
241241
len([r for r in result.rows if r.type in ("TIER-0", "PRIV")])
242-
status_text = f"[green][/] {target} ({task_count} tasks)"
242+
status_text = f"[green][+][/] {target} ({task_count} tasks)"
243243
else:
244244
error_short = (result.error or "Error")[:30]
245-
status_text = f"[red][/] {target}: {error_short}"
245+
status_text = f"[red][-][/] {target}: {error_short}"
246246

247247
self._progress.update(self._task_id, advance=1, status=status_text)
248248

@@ -425,15 +425,15 @@ def _run_sequential(
425425
self._completed += 1
426426
if result.skipped:
427427
self._skipped += 1
428-
status_text = f"[yellow][/] {target} [dim](skipped)[/]"
428+
status_text = f"[yellow][~][/] {target} [dim](skipped)[/]"
429429
elif result.success:
430430
self._succeeded += 1
431431
task_count = len([r for r in result.rows if r.type not in ("FAILURE", None)])
432-
status_text = f"[green][/] {target} ({task_count} tasks)"
432+
status_text = f"[green][+][/] {target} ({task_count} tasks)"
433433
else:
434434
self._failed += 1
435435
error_short = (result.error or "Error")[:30]
436-
status_text = f"[red][/] {target}: {error_short}"
436+
status_text = f"[red][-][/] {target}: {error_short}"
437437

438438
progress.update(task_id, advance=1, status=status_text)
439439

taskhound/engine/offline.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ def _process_offline_host(
296296
if not result.should_include:
297297
continue
298298

299+
# Update row with classification results (like online mode does)
300+
row.type = result.task_type
301+
row.reason = result.reason
302+
row.password_analysis = result.password_analysis
303+
299304
# Format output block based on classification
300305
if result.task_type in ("TIER-0", "PRIV"):
301306
priv_lines.extend(

taskhound/opengraph/builder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ def _query_bloodhound_with_sid_validation(names_with_sids: Dict[str, str], node_
603603

604604
if len(node_list) > 1:
605605
# DUPLICATE NAMES DETECTED!
606-
warn(f"⚠️ Duplicate {node_type} nodes found for {name}: {len(node_list)} nodes")
606+
warn(f"[!] Duplicate {node_type} nodes found for {name}: {len(node_list)} nodes")
607607
warn(f" Node IDs: {[n[0] for n in node_list]}")
608608
warn(f" SIDs: {[n[1] for n in node_list]}")
609609

@@ -619,7 +619,7 @@ def _query_bloodhound_with_sid_validation(names_with_sids: Dict[str, str], node_
619619
debug(f"[+] Validated {name} → node_id={matched_node[0]}, SID={matched_node[1]}")
620620
else:
621621
# SID mismatch - wrong computer!
622-
warn(f"⚠️ SID mismatch for {name}:")
622+
warn(f"[!] SID mismatch for {name}:")
623623
warn(f" Expected SID: {expected_sid}")
624624
warn(f" BloodHound returned: {[n[1] for n in node_list]}")
625625
warn(" Skipping this node (possible stale data or wrong computer)")

0 commit comments

Comments
 (0)