mirror of
https://github.com/overcuriousity/trace.git
synced 2025-12-20 13:02:21 +00:00
Merge pull request #14 from overcuriousity/claude/enhance-cli-interface-nYnNL
Claude/enhance cli interface n yn nl
This commit is contained in:
69
CLAUDE.md
69
CLAUDE.md
@@ -9,20 +9,75 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
### Running the Application
|
### Running the Application
|
||||||
|
|
||||||
|
#### Launching TUI
|
||||||
```bash
|
```bash
|
||||||
# Run directly from source
|
# Launch TUI (default behavior)
|
||||||
python3 main.py
|
python3 main.py
|
||||||
|
|
||||||
# Quick CLI note addition (requires active case/evidence set in TUI)
|
|
||||||
python3 main.py "Your note content here"
|
|
||||||
|
|
||||||
# Export to markdown
|
|
||||||
python3 main.py --export --output report.md
|
|
||||||
|
|
||||||
# Open TUI directly at active case/evidence
|
# Open TUI directly at active case/evidence
|
||||||
python3 main.py --open
|
python3 main.py --open
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Context Management
|
||||||
|
```bash
|
||||||
|
# Show current active case and evidence
|
||||||
|
python3 main.py --show-context
|
||||||
|
|
||||||
|
# List all cases and evidence in hierarchy
|
||||||
|
python3 main.py --list
|
||||||
|
|
||||||
|
# Switch active case (by case number or UUID)
|
||||||
|
python3 main.py --switch-case 2024-001
|
||||||
|
|
||||||
|
# Switch active evidence (by name or UUID, within active case)
|
||||||
|
python3 main.py --switch-evidence "disk-image-1"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Creating Cases and Evidence
|
||||||
|
```bash
|
||||||
|
# Create new case (automatically sets as active)
|
||||||
|
python3 main.py --new-case 2024-001
|
||||||
|
|
||||||
|
# Create case with metadata
|
||||||
|
python3 main.py --new-case 2024-001 --name "Ransomware Investigation" --investigator "John Doe"
|
||||||
|
|
||||||
|
# Create evidence in active case (automatically sets as active)
|
||||||
|
python3 main.py --new-evidence "Laptop HDD"
|
||||||
|
|
||||||
|
# Create evidence with description
|
||||||
|
python3 main.py --new-evidence "Server Logs" --description "Apache access logs from compromised server"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Adding Notes
|
||||||
|
```bash
|
||||||
|
# Quick note to active context
|
||||||
|
python3 main.py "Observed suspicious process at 14:32"
|
||||||
|
|
||||||
|
# Read note from stdin (for piping command output)
|
||||||
|
echo "Network spike detected" | python3 main.py --stdin
|
||||||
|
ps aux | grep malware | python3 main.py --stdin
|
||||||
|
tail -f logfile.txt | grep error | python3 main.py --stdin
|
||||||
|
|
||||||
|
# Add note to specific case without changing active context
|
||||||
|
python3 main.py --case 2024-002 "Found malware in temp folder"
|
||||||
|
|
||||||
|
# Add note to specific evidence without changing active context
|
||||||
|
python3 main.py --evidence "disk-image-2" "Bad sectors detected"
|
||||||
|
|
||||||
|
# Add note to specific case and evidence (both overrides)
|
||||||
|
python3 main.py --case 2024-001 --evidence "Laptop HDD" "Recovered deleted files"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Export
|
||||||
|
```bash
|
||||||
|
# Export all data to markdown
|
||||||
|
python3 main.py --export --output report.md
|
||||||
|
|
||||||
|
# Export with default filename (trace_export.md)
|
||||||
|
python3 main.py --export
|
||||||
|
```
|
||||||
|
|
||||||
### Building Binary
|
### Building Binary
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies first
|
# Install dependencies first
|
||||||
|
|||||||
70
README.md
70
README.md
@@ -22,6 +22,76 @@ trace "Observed outbound connection to 192.168.1.55 on port 80. #suspicious #net
|
|||||||
|
|
||||||
**System Integrity Chain:** Each command-line note is immediately stamped, concatenated with its content, and hashed using SHA256 before storage. This ensures a non-repudiable log entry.
|
**System Integrity Chain:** Each command-line note is immediately stamped, concatenated with its content, and hashed using SHA256 before storage. This ensures a non-repudiable log entry.
|
||||||
|
|
||||||
|
## CLI Command Reference
|
||||||
|
|
||||||
|
### Context Management
|
||||||
|
|
||||||
|
View and switch between cases and evidence without opening the TUI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show current active case and evidence
|
||||||
|
trace --show-context
|
||||||
|
|
||||||
|
# List all cases and evidence in hierarchy
|
||||||
|
trace --list
|
||||||
|
|
||||||
|
# Switch active case (by case number or UUID)
|
||||||
|
trace --switch-case 2024-001
|
||||||
|
|
||||||
|
# Switch active evidence (by name or UUID, within active case)
|
||||||
|
trace --switch-evidence "disk-image-1"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Case and Evidence Creation
|
||||||
|
|
||||||
|
Create new cases and evidence directly from the command line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create new case (automatically becomes active)
|
||||||
|
trace --new-case 2024-001
|
||||||
|
|
||||||
|
# Create case with full metadata
|
||||||
|
trace --new-case 2024-001 --name "Ransomware Investigation" --investigator "Jane Doe"
|
||||||
|
|
||||||
|
# Create evidence in active case (automatically becomes active)
|
||||||
|
trace --new-evidence "Laptop HDD"
|
||||||
|
|
||||||
|
# Create evidence with description
|
||||||
|
trace --new-evidence "Server Logs" --description "Apache logs from compromised server"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Note-Taking
|
||||||
|
|
||||||
|
Beyond basic hot logging, trace supports stdin piping and context overrides:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pipe command output directly into notes
|
||||||
|
ps aux | grep malware | trace --stdin
|
||||||
|
tail -f /var/log/auth.log | grep "Failed password" | trace --stdin
|
||||||
|
netstat -an | trace --stdin
|
||||||
|
|
||||||
|
# Add note to specific case without changing active context
|
||||||
|
trace --case 2024-002 "Found malware in temp folder"
|
||||||
|
|
||||||
|
# Add note to specific evidence without changing active context
|
||||||
|
trace --evidence "Memory Dump" "Suspicious process identified"
|
||||||
|
|
||||||
|
# Override both case and evidence for a single note
|
||||||
|
trace --case 2024-001 --evidence "Disk Image" "Recovered deleted files"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Identifiers:** All commands accept both human-friendly identifiers (case numbers like `2024-001`, evidence names like `Laptop HDD`) and UUIDs. Use `--list` to see available identifiers.
|
||||||
|
|
||||||
|
### Export
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Export all data to markdown (GPG-signed if enabled)
|
||||||
|
trace --export --output investigation-report.md
|
||||||
|
|
||||||
|
# Export with default filename (trace_export.md)
|
||||||
|
trace --export
|
||||||
|
```
|
||||||
|
|
||||||
## Installation & Deployment
|
## Installation & Deployment
|
||||||
|
|
||||||
### Quick Install from Latest Release
|
### Quick Install from Latest Release
|
||||||
|
|||||||
317
trace/cli.py
317
trace/cli.py
@@ -1,11 +1,204 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from .models import Note, Case
|
from typing import Optional, Tuple
|
||||||
|
from .models import Note, Case, Evidence
|
||||||
from .storage import Storage, StateManager
|
from .storage import Storage, StateManager
|
||||||
from .crypto import Crypto
|
from .crypto import Crypto
|
||||||
|
|
||||||
def quick_add_note(content: str):
|
def find_case(storage: Storage, identifier: str) -> Optional[Case]:
|
||||||
|
"""Find a case by case_id (UUID) or case_number."""
|
||||||
|
for case in storage.cases:
|
||||||
|
if case.case_id == identifier or case.case_number == identifier:
|
||||||
|
return case
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_evidence(case: Case, identifier: str) -> Optional[Evidence]:
|
||||||
|
"""Find evidence by evidence_id (UUID) or name within a case."""
|
||||||
|
for evidence in case.evidence:
|
||||||
|
if evidence.evidence_id == identifier or evidence.name == identifier:
|
||||||
|
return evidence
|
||||||
|
return None
|
||||||
|
|
||||||
|
def show_context():
|
||||||
|
"""Display the current active context."""
|
||||||
|
state_manager = StateManager()
|
||||||
|
storage = Storage()
|
||||||
|
|
||||||
|
state = state_manager.get_active()
|
||||||
|
case_id = state.get("case_id")
|
||||||
|
evidence_id = state.get("evidence_id")
|
||||||
|
|
||||||
|
if not case_id:
|
||||||
|
print("No active context set.")
|
||||||
|
print("Use --switch-case to set an active case, or open the TUI to select one.")
|
||||||
|
return
|
||||||
|
|
||||||
|
case = storage.get_case(case_id)
|
||||||
|
if not case:
|
||||||
|
print("Error: Active case not found in storage.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Active context:")
|
||||||
|
print(f" Case: {case.case_number}", end="")
|
||||||
|
if case.name:
|
||||||
|
print(f" - {case.name}", end="")
|
||||||
|
print(f" [{case.case_id[:8]}...]")
|
||||||
|
|
||||||
|
if evidence_id:
|
||||||
|
evidence = find_evidence(case, evidence_id)
|
||||||
|
if evidence:
|
||||||
|
print(f" Evidence: {evidence.name}", end="")
|
||||||
|
if evidence.description:
|
||||||
|
print(f" - {evidence.description}", end="")
|
||||||
|
print(f" [{evidence.evidence_id[:8]}...]")
|
||||||
|
else:
|
||||||
|
print(f" Evidence: [not found - stale reference]")
|
||||||
|
else:
|
||||||
|
print(f" Evidence: [none - notes will attach to case]")
|
||||||
|
|
||||||
|
def list_contexts():
|
||||||
|
"""List all cases and their evidence in a hierarchical format."""
|
||||||
|
storage = Storage()
|
||||||
|
|
||||||
|
if not storage.cases:
|
||||||
|
print("No cases found.")
|
||||||
|
print("Use --new-case to create one, or open the TUI.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Cases and Evidence:")
|
||||||
|
for case in storage.cases:
|
||||||
|
# Show case
|
||||||
|
print(f" [{case.case_id[:8]}...] {case.case_number}", end="")
|
||||||
|
if case.name:
|
||||||
|
print(f" - {case.name}", end="")
|
||||||
|
if case.investigator:
|
||||||
|
print(f" (Investigator: {case.investigator})", end="")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Show evidence under this case
|
||||||
|
for evidence in case.evidence:
|
||||||
|
print(f" [{evidence.evidence_id[:8]}...] {evidence.name}", end="")
|
||||||
|
if evidence.description:
|
||||||
|
print(f" - {evidence.description}", end="")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Add blank line between cases for readability
|
||||||
|
if storage.cases[-1] != case:
|
||||||
|
print()
|
||||||
|
|
||||||
|
def create_case(case_number: str, name: Optional[str] = None, investigator: Optional[str] = None):
|
||||||
|
"""Create a new case and set it as active."""
|
||||||
|
storage = Storage()
|
||||||
|
state_manager = StateManager()
|
||||||
|
|
||||||
|
# Check if case number already exists
|
||||||
|
existing = find_case(storage, case_number)
|
||||||
|
if existing:
|
||||||
|
print(f"Error: Case with number '{case_number}' already exists.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Create new case
|
||||||
|
case = Case(case_number=case_number, name=name, investigator=investigator)
|
||||||
|
storage.cases.append(case)
|
||||||
|
storage.save_data()
|
||||||
|
|
||||||
|
# Set as active case
|
||||||
|
state_manager.set_active(case.case_id, None)
|
||||||
|
|
||||||
|
print(f"✓ Created case '{case_number}' [{case.case_id[:8]}...]")
|
||||||
|
if name:
|
||||||
|
print(f" Name: {name}")
|
||||||
|
if investigator:
|
||||||
|
print(f" Investigator: {investigator}")
|
||||||
|
print(f"✓ Set as active case")
|
||||||
|
|
||||||
|
def create_evidence(name: str, description: Optional[str] = None):
|
||||||
|
"""Create new evidence and attach to active case."""
|
||||||
|
storage = Storage()
|
||||||
|
state_manager = StateManager()
|
||||||
|
|
||||||
|
state = state_manager.get_active()
|
||||||
|
case_id = state.get("case_id")
|
||||||
|
|
||||||
|
if not case_id:
|
||||||
|
print("Error: No active case set. Use --switch-case or --new-case first.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
case = storage.get_case(case_id)
|
||||||
|
if not case:
|
||||||
|
print("Error: Active case not found in storage.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Check if evidence with this name already exists in the case
|
||||||
|
existing = find_evidence(case, name)
|
||||||
|
if existing:
|
||||||
|
print(f"Error: Evidence named '{name}' already exists in case '{case.case_number}'.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Create new evidence
|
||||||
|
evidence = Evidence(name=name, description=description)
|
||||||
|
case.evidence.append(evidence)
|
||||||
|
storage.save_data()
|
||||||
|
|
||||||
|
# Set as active evidence
|
||||||
|
state_manager.set_active(case.case_id, evidence.evidence_id)
|
||||||
|
|
||||||
|
print(f"✓ Created evidence '{name}' [{evidence.evidence_id[:8]}...]")
|
||||||
|
if description:
|
||||||
|
print(f" Description: {description}")
|
||||||
|
print(f"✓ Added to case '{case.case_number}'")
|
||||||
|
print(f"✓ Set as active evidence")
|
||||||
|
|
||||||
|
def switch_case(identifier: str):
|
||||||
|
"""Switch active case context."""
|
||||||
|
storage = Storage()
|
||||||
|
state_manager = StateManager()
|
||||||
|
|
||||||
|
case = find_case(storage, identifier)
|
||||||
|
if not case:
|
||||||
|
print(f"Error: Case '{identifier}' not found.", file=sys.stderr)
|
||||||
|
print("Use --list to see available cases.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Set as active case, clear evidence
|
||||||
|
state_manager.set_active(case.case_id, None)
|
||||||
|
|
||||||
|
print(f"✓ Switched to case '{case.case_number}' [{case.case_id[:8]}...]")
|
||||||
|
if case.name:
|
||||||
|
print(f" {case.name}")
|
||||||
|
|
||||||
|
def switch_evidence(identifier: str):
|
||||||
|
"""Switch active evidence context within the active case."""
|
||||||
|
storage = Storage()
|
||||||
|
state_manager = StateManager()
|
||||||
|
|
||||||
|
state = state_manager.get_active()
|
||||||
|
case_id = state.get("case_id")
|
||||||
|
|
||||||
|
if not case_id:
|
||||||
|
print("Error: No active case set. Use --switch-case first.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
case = storage.get_case(case_id)
|
||||||
|
if not case:
|
||||||
|
print("Error: Active case not found in storage.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
evidence = find_evidence(case, identifier)
|
||||||
|
if not evidence:
|
||||||
|
print(f"Error: Evidence '{identifier}' not found in case '{case.case_number}'.", file=sys.stderr)
|
||||||
|
print("Use --list to see available evidence.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Set as active evidence
|
||||||
|
state_manager.set_active(case.case_id, evidence.evidence_id)
|
||||||
|
|
||||||
|
print(f"✓ Switched to evidence '{evidence.name}' [{evidence.evidence_id[:8]}...]")
|
||||||
|
if evidence.description:
|
||||||
|
print(f" {evidence.description}")
|
||||||
|
|
||||||
|
def quick_add_note(content: str, case_override: Optional[str] = None, evidence_override: Optional[str] = None):
|
||||||
storage = Storage()
|
storage = Storage()
|
||||||
state_manager = StateManager()
|
state_manager = StateManager()
|
||||||
|
|
||||||
@@ -17,31 +210,43 @@ def quick_add_note(content: str):
|
|||||||
state = state_manager.get_active()
|
state = state_manager.get_active()
|
||||||
settings = state_manager.get_settings()
|
settings = state_manager.get_settings()
|
||||||
|
|
||||||
|
# Handle case override or use active case
|
||||||
|
if case_override:
|
||||||
|
case = find_case(storage, case_override)
|
||||||
|
if not case:
|
||||||
|
print(f"Error: Case '{case_override}' not found.", file=sys.stderr)
|
||||||
|
print("Use --list to see available cases.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
case_id = state.get("case_id")
|
case_id = state.get("case_id")
|
||||||
evidence_id = state.get("evidence_id")
|
|
||||||
|
|
||||||
if not case_id:
|
if not case_id:
|
||||||
print("Error: No active case set. Open the TUI to select a case first.", file=sys.stderr)
|
print("Error: No active case set. Use --switch-case, --new-case, or open the TUI to select a case.", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
case = storage.get_case(case_id)
|
case = storage.get_case(case_id)
|
||||||
if not case:
|
if not case:
|
||||||
print("Error: Active case not found in storage. Ensure you have set an active case in the TUI.", file=sys.stderr)
|
print("Error: Active case not found in storage.", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Handle evidence override or use active evidence
|
||||||
target_evidence = None
|
target_evidence = None
|
||||||
|
|
||||||
|
if evidence_override:
|
||||||
|
target_evidence = find_evidence(case, evidence_override)
|
||||||
|
if not target_evidence:
|
||||||
|
print(f"Error: Evidence '{evidence_override}' not found in case '{case.case_number}'.", file=sys.stderr)
|
||||||
|
print("Use --list to see available evidence.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
elif not case_override: # Only use active evidence if not overriding case
|
||||||
|
evidence_id = state.get("evidence_id")
|
||||||
if evidence_id:
|
if evidence_id:
|
||||||
# Find and validate evidence belongs to active case
|
# Find and validate evidence belongs to active case
|
||||||
for ev in case.evidence:
|
target_evidence = find_evidence(case, evidence_id)
|
||||||
if ev.evidence_id == evidence_id:
|
|
||||||
target_evidence = ev
|
|
||||||
break
|
|
||||||
|
|
||||||
if not target_evidence:
|
if not target_evidence:
|
||||||
# Evidence ID is set but doesn't exist in case - clear it
|
# Evidence ID is set but doesn't exist in case - clear it
|
||||||
print(f"Warning: Active evidence not found in case. Clearing to case level.", file=sys.stderr)
|
print(f"Warning: Active evidence not found in case. Clearing to case level.", file=sys.stderr)
|
||||||
state_manager.set_active(case_id, None)
|
state_manager.set_active(case.case_id, None)
|
||||||
|
|
||||||
# Create note
|
# Create note
|
||||||
note = Note(content=content)
|
note = Note(content=content)
|
||||||
@@ -67,10 +272,6 @@ def quick_add_note(content: str):
|
|||||||
if target_evidence:
|
if target_evidence:
|
||||||
target_evidence.notes.append(note)
|
target_evidence.notes.append(note)
|
||||||
print(f"✓ Note added to evidence '{target_evidence.name}'")
|
print(f"✓ Note added to evidence '{target_evidence.name}'")
|
||||||
elif evidence_id:
|
|
||||||
print("Warning: Active evidence not found. Adding to case instead.")
|
|
||||||
case.notes.append(note)
|
|
||||||
print(f"✓ Note added to case '{case.case_number}'")
|
|
||||||
else:
|
else:
|
||||||
case.notes.append(note)
|
case.notes.append(note)
|
||||||
print(f"✓ Note added to case '{case.case_number}'")
|
print(f"✓ Note added to case '{case.case_number}'")
|
||||||
@@ -182,25 +383,93 @@ def format_note_for_export(note: Note) -> str:
|
|||||||
return "".join(lines)
|
return "".join(lines)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="trace: Forensic Note Taking Tool")
|
parser = argparse.ArgumentParser(
|
||||||
parser.add_argument("note", nargs="?", help="Quick note content to add to active context")
|
description="trace: Forensic Note Taking Tool",
|
||||||
parser.add_argument("--export", help="Export all data to Markdown file", action="store_true")
|
epilog="Examples:\n"
|
||||||
parser.add_argument("--output", help="Output file for export", default="trace_export.md")
|
" trace 'Found suspicious process' Add note to active context\n"
|
||||||
parser.add_argument("--open", "-o", help="Open TUI directly at active case/evidence", action="store_true")
|
" trace --stdin < output.txt Add file contents as note\n"
|
||||||
|
" trace --list List all cases and evidence\n"
|
||||||
|
" trace --new-case 2024-001 Create new case\n"
|
||||||
|
" trace --switch-case 2024-001 Switch active case\n",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter
|
||||||
|
)
|
||||||
|
|
||||||
# We will import TUI only if needed to keep start time fast
|
# Note content (positional or stdin)
|
||||||
|
parser.add_argument("note", nargs="?", help="Quick note content to add to active context")
|
||||||
|
parser.add_argument("--stdin", action="store_true", help="Read note content from stdin")
|
||||||
|
|
||||||
|
# Context management
|
||||||
|
parser.add_argument("--show-context", action="store_true", help="Show active case and evidence")
|
||||||
|
parser.add_argument("--list", action="store_true", help="List all cases and evidence")
|
||||||
|
parser.add_argument("--switch-case", metavar="IDENTIFIER", help="Switch active case (by ID or case number)")
|
||||||
|
parser.add_argument("--switch-evidence", metavar="IDENTIFIER", help="Switch active evidence (by ID or name)")
|
||||||
|
|
||||||
|
# Temporary overrides for note addition
|
||||||
|
parser.add_argument("--case", metavar="IDENTIFIER", help="Use specific case for this note (doesn't change active)")
|
||||||
|
parser.add_argument("--evidence", metavar="IDENTIFIER", help="Use specific evidence for this note (doesn't change active)")
|
||||||
|
|
||||||
|
# Case and evidence creation
|
||||||
|
parser.add_argument("--new-case", metavar="CASE_NUMBER", help="Create new case")
|
||||||
|
parser.add_argument("--name", metavar="NAME", help="Name for new case")
|
||||||
|
parser.add_argument("--investigator", metavar="INVESTIGATOR", help="Investigator name for new case")
|
||||||
|
parser.add_argument("--new-evidence", metavar="EVIDENCE_NAME", help="Create new evidence in active case")
|
||||||
|
parser.add_argument("--description", metavar="DESC", help="Description for new evidence")
|
||||||
|
|
||||||
|
# Export
|
||||||
|
parser.add_argument("--export", action="store_true", help="Export all data to Markdown file")
|
||||||
|
parser.add_argument("--output", metavar="FILE", default="trace_export.md", help="Output file for export")
|
||||||
|
|
||||||
|
# TUI
|
||||||
|
parser.add_argument("--open", "-o", action="store_true", help="Open TUI directly at active case/evidence")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Handle context management commands
|
||||||
|
if args.show_context:
|
||||||
|
show_context()
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.list:
|
||||||
|
list_contexts()
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.switch_case:
|
||||||
|
switch_case(args.switch_case)
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.switch_evidence:
|
||||||
|
switch_evidence(args.switch_evidence)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle case/evidence creation
|
||||||
|
if args.new_case:
|
||||||
|
create_case(args.new_case, name=args.name, investigator=args.investigator)
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.new_evidence:
|
||||||
|
create_evidence(args.new_evidence, description=args.description)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle export
|
||||||
if args.export:
|
if args.export:
|
||||||
export_markdown(args.output)
|
export_markdown(args.output)
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.note:
|
# Handle note addition
|
||||||
quick_add_note(args.note)
|
if args.stdin:
|
||||||
|
# Read from stdin
|
||||||
|
content = sys.stdin.read().strip()
|
||||||
|
if not content:
|
||||||
|
print("Error: No content provided from stdin.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
quick_add_note(content, case_override=args.case, evidence_override=args.evidence)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check for first run and run GPG wizard if needed
|
if args.note:
|
||||||
|
quick_add_note(args.note, case_override=args.case, evidence_override=args.evidence)
|
||||||
|
return
|
||||||
|
|
||||||
|
# No arguments - check for first run and launch TUI
|
||||||
from .gpg_wizard import check_and_run_wizard
|
from .gpg_wizard import check_and_run_wizard
|
||||||
check_and_run_wizard()
|
check_and_run_wizard()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user