footprinter-cli 1.0.0__py3-none-any.whl
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.
- footprinter/__init__.py +8 -0
- footprinter/access.py +444 -0
- footprinter/api/__init__.py +1 -0
- footprinter/api/db.py +61 -0
- footprinter/api/entities.py +250 -0
- footprinter/api/search.py +47 -0
- footprinter/api/semantic.py +33 -0
- footprinter/api/server.py +66 -0
- footprinter/api/status.py +15 -0
- footprinter/bundled/__init__.py +0 -0
- footprinter/bundled/config.example.yaml +161 -0
- footprinter/bundled/patterns/context_patterns.yaml +18 -0
- footprinter/bundled/patterns/extensions.yaml +283 -0
- footprinter/bundled/patterns/filename_patterns.yaml +61 -0
- footprinter/bundled/patterns/mime_mappings.yaml +68 -0
- footprinter/bundled/patterns/salesforce_rules.yaml +84 -0
- footprinter/bundled/patterns/security_patterns.yaml +27 -0
- footprinter/cli/__init__.py +128 -0
- footprinter/cli/__main__.py +6 -0
- footprinter/cli/_common.py +332 -0
- footprinter/cli/_policy_helpers.py +646 -0
- footprinter/cli/_prompt.py +220 -0
- footprinter/cli/api_cmd.py +32 -0
- footprinter/cli/connect.py +591 -0
- footprinter/cli/data.py +879 -0
- footprinter/cli/delete.py +128 -0
- footprinter/cli/ingest.py +579 -0
- footprinter/cli/mcp_cmd.py +750 -0
- footprinter/cli/mcp_setup.py +306 -0
- footprinter/cli/search.py +393 -0
- footprinter/cli/search_cmd.py +69 -0
- footprinter/cli/setup.py +1836 -0
- footprinter/cli/status.py +729 -0
- footprinter/cli/status_cmd.py +104 -0
- footprinter/cli/upsert.py +794 -0
- footprinter/cli/vectorize_cmd.py +215 -0
- footprinter/cli/view.py +322 -0
- footprinter/connectors/__init__.py +171 -0
- footprinter/connectors/config_utils.py +141 -0
- footprinter/db/__init__.py +37 -0
- footprinter/db/browser.py +198 -0
- footprinter/db/chats.py +610 -0
- footprinter/db/clients.py +307 -0
- footprinter/db/emails.py +279 -0
- footprinter/db/files.py +741 -0
- footprinter/db/folders.py +659 -0
- footprinter/db/messages.py +192 -0
- footprinter/db/policies.py +151 -0
- footprinter/db/projects.py +673 -0
- footprinter/db/search.py +573 -0
- footprinter/db/sql_utils.py +168 -0
- footprinter/db/status.py +320 -0
- footprinter/db/uploads.py +70 -0
- footprinter/ingest/__init__.py +0 -0
- footprinter/ingest/adapters/__init__.py +33 -0
- footprinter/ingest/adapters/browser.py +54 -0
- footprinter/ingest/adapters/chat.py +57 -0
- footprinter/ingest/adapters/ingest.py +146 -0
- footprinter/ingest/adapters/local_files.py +68 -0
- footprinter/ingest/adapters/local_folders.py +52 -0
- footprinter/ingest/adapters/protocol.py +174 -0
- footprinter/ingest/browser_indexer.py +216 -0
- footprinter/ingest/chat_dedup.py +156 -0
- footprinter/ingest/chat_indexer.py +515 -0
- footprinter/ingest/chat_parsers/__init__.py +8 -0
- footprinter/ingest/chat_parsers/chatgpt_parser.py +229 -0
- footprinter/ingest/chat_parsers/claude_parser.py +161 -0
- footprinter/ingest/cli.py +827 -0
- footprinter/ingest/content_extractors.py +117 -0
- footprinter/ingest/database.py +36 -0
- footprinter/ingest/db/__init__.py +1 -0
- footprinter/ingest/db/connector_schema.py +47 -0
- footprinter/ingest/db/migration.py +328 -0
- footprinter/ingest/db/schema.py +1043 -0
- footprinter/ingest/db/security.py +6 -0
- footprinter/ingest/file_indexer.py +261 -0
- footprinter/ingest/file_scanner.py +277 -0
- footprinter/ingest/folder_indexer.py +226 -0
- footprinter/ingest/full_content_extractor.py +321 -0
- footprinter/ingest/orchestrator.py +125 -0
- footprinter/ingest/pipe_runner.py +217 -0
- footprinter/ingest/processing.py +165 -0
- footprinter/ingest/registry.py +201 -0
- footprinter/ingest/run_record.py +91 -0
- footprinter/ingest/status.py +346 -0
- footprinter/mcp/__init__.py +0 -0
- footprinter/mcp/__main__.py +5 -0
- footprinter/mcp/db.py +57 -0
- footprinter/mcp/errors.py +102 -0
- footprinter/mcp/extraction.py +226 -0
- footprinter/mcp/server.py +39 -0
- footprinter/mcp/tools/__init__.py +0 -0
- footprinter/mcp/tools/navigation.py +70 -0
- footprinter/mcp/tools/read.py +75 -0
- footprinter/mcp/tools/search.py +158 -0
- footprinter/mcp/tools/semantic.py +79 -0
- footprinter/mcp/tools/status.py +15 -0
- footprinter/paths.py +91 -0
- footprinter/permissions.py +1160 -0
- footprinter/semantic/__init__.py +13 -0
- footprinter/semantic/chunking.py +52 -0
- footprinter/semantic/embeddings.py +23 -0
- footprinter/semantic/hybrid_search.py +273 -0
- footprinter/semantic/vector_store.py +471 -0
- footprinter/services/__init__.py +49 -0
- footprinter/services/access_service.py +342 -0
- footprinter/services/chat_service.py +85 -0
- footprinter/services/client_service.py +267 -0
- footprinter/services/content_service.py +181 -0
- footprinter/services/email_service.py +89 -0
- footprinter/services/file_service.py +83 -0
- footprinter/services/folder_service.py +122 -0
- footprinter/services/includes.py +19 -0
- footprinter/services/ingest_service.py +231 -0
- footprinter/services/project_service.py +262 -0
- footprinter/services/roles.py +25 -0
- footprinter/services/search_service.py +177 -0
- footprinter/services/semantic_service.py +360 -0
- footprinter/services/status_service.py +18 -0
- footprinter/services/visit_service.py +65 -0
- footprinter/source_registry.py +194 -0
- footprinter/utils/__init__.py +7 -0
- footprinter/utils/hash_utils.py +59 -0
- footprinter/utils/logging_config.py +68 -0
- footprinter/utils/mime.py +30 -0
- footprinter/utils/text.py +6 -0
- footprinter/utils/time.py +11 -0
- footprinter/visibility.py +1272 -0
- footprinter_cli-1.0.0.dist-info/LICENSE +21 -0
- footprinter_cli-1.0.0.dist-info/METADATA +229 -0
- footprinter_cli-1.0.0.dist-info/RECORD +134 -0
- footprinter_cli-1.0.0.dist-info/WHEEL +5 -0
- footprinter_cli-1.0.0.dist-info/entry_points.txt +2 -0
- footprinter_cli-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Safe prompt wrappers with Escape key and Ctrl+C/Ctrl+D support.
|
|
2
|
+
|
|
3
|
+
Wraps Rich's ``Prompt`` and ``Confirm`` to detect Escape (via raw terminal)
|
|
4
|
+
and convert ``KeyboardInterrupt``/``EOFError`` into ``PromptCancelled``.
|
|
5
|
+
|
|
6
|
+
``PromptCancelled`` inherits from ``BaseException`` so it passes through the
|
|
7
|
+
~9 ``except Exception`` broad catches in the setup wizard without being
|
|
8
|
+
swallowed.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from rich.prompt import Confirm, Prompt
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import select
|
|
18
|
+
import termios
|
|
19
|
+
import tty
|
|
20
|
+
|
|
21
|
+
_HAS_TERMIOS = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
# Windows or other non-POSIX — fall back to input()
|
|
24
|
+
select = None # type: ignore[assignment]
|
|
25
|
+
termios = None # type: ignore[assignment]
|
|
26
|
+
tty = None # type: ignore[assignment]
|
|
27
|
+
_HAS_TERMIOS = False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PromptCancelled(BaseException):
|
|
31
|
+
"""Raised when the user presses Escape, Ctrl+C, or Ctrl+D at a prompt.
|
|
32
|
+
|
|
33
|
+
Inherits from ``BaseException`` (not ``Exception``) so it propagates
|
|
34
|
+
through ``except Exception`` blocks in wizard steps.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
_ESCAPE_TIMEOUT = 0.05 # 50ms to distinguish bare Escape from escape sequences
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _read_utf8_char(fd: int, lead: int) -> tuple[str, bytes]:
|
|
42
|
+
"""Read a complete UTF-8 character given its lead byte.
|
|
43
|
+
|
|
44
|
+
Determines the expected continuation byte count from the lead byte
|
|
45
|
+
pattern, reads that many bytes via ``_read_with_timeout``, and decodes.
|
|
46
|
+
|
|
47
|
+
Returns ``(char, leftover)`` where *char* is the decoded character
|
|
48
|
+
(empty string on failure) and *leftover* is a non-continuation byte
|
|
49
|
+
that was consumed but could not be used — the caller must re-process it.
|
|
50
|
+
"""
|
|
51
|
+
if 0xC0 <= lead <= 0xDF:
|
|
52
|
+
n = 1
|
|
53
|
+
elif 0xE0 <= lead <= 0xEF:
|
|
54
|
+
n = 2
|
|
55
|
+
elif 0xF0 <= lead <= 0xF7:
|
|
56
|
+
n = 3
|
|
57
|
+
else:
|
|
58
|
+
return ("", b"")
|
|
59
|
+
|
|
60
|
+
raw = bytes([lead])
|
|
61
|
+
for _ in range(n):
|
|
62
|
+
b = _read_with_timeout(fd, _ESCAPE_TIMEOUT)
|
|
63
|
+
if not b:
|
|
64
|
+
return ("", b"")
|
|
65
|
+
if not (0x80 <= b[0] <= 0xBF):
|
|
66
|
+
return ("", b) # Return consumed byte for re-processing
|
|
67
|
+
raw += b
|
|
68
|
+
|
|
69
|
+
decoded = raw.decode("utf-8", errors="replace")
|
|
70
|
+
if "\ufffd" in decoded:
|
|
71
|
+
return ("", b"")
|
|
72
|
+
return (decoded, b"")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _read_with_timeout(fd: int, timeout: float) -> bytes:
|
|
76
|
+
"""Read a single byte from *fd* with a timeout via ``select``."""
|
|
77
|
+
ready, _, _ = select.select([fd], [], [], timeout)
|
|
78
|
+
if ready:
|
|
79
|
+
return os.read(fd, 1)
|
|
80
|
+
return b""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _safe_readline(password: bool = False) -> str:
|
|
84
|
+
"""Read a line from stdin with Escape/Ctrl+C/Ctrl+D detection.
|
|
85
|
+
|
|
86
|
+
Uses raw terminal mode on POSIX systems. Falls back to ``input()``
|
|
87
|
+
when termios is unavailable (Windows, non-TTY, piped input).
|
|
88
|
+
"""
|
|
89
|
+
if not _HAS_TERMIOS or not sys.stdin.isatty():
|
|
90
|
+
try:
|
|
91
|
+
return input()
|
|
92
|
+
except (KeyboardInterrupt, EOFError) as exc:
|
|
93
|
+
raise PromptCancelled(str(exc)) from exc
|
|
94
|
+
|
|
95
|
+
fd = sys.stdin.fileno()
|
|
96
|
+
old_settings = termios.tcgetattr(fd)
|
|
97
|
+
buf: list[str] = []
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
tty.setraw(fd)
|
|
101
|
+
pending = b""
|
|
102
|
+
|
|
103
|
+
while True:
|
|
104
|
+
if pending:
|
|
105
|
+
ch = pending
|
|
106
|
+
pending = b""
|
|
107
|
+
else:
|
|
108
|
+
ready, _, _ = select.select([fd], [], [])
|
|
109
|
+
if not ready:
|
|
110
|
+
continue
|
|
111
|
+
ch = os.read(fd, 1)
|
|
112
|
+
|
|
113
|
+
if not ch:
|
|
114
|
+
raise PromptCancelled("EOF")
|
|
115
|
+
|
|
116
|
+
byte = ch[0]
|
|
117
|
+
|
|
118
|
+
if byte == 0x1B: # Escape
|
|
119
|
+
# Check if more bytes follow (escape sequence vs bare Escape)
|
|
120
|
+
follow = _read_with_timeout(fd, _ESCAPE_TIMEOUT)
|
|
121
|
+
if not follow:
|
|
122
|
+
# Bare Escape — user pressed Esc
|
|
123
|
+
raise PromptCancelled("Escape")
|
|
124
|
+
# Escape sequence — consume and ignore
|
|
125
|
+
if follow == b"[":
|
|
126
|
+
# CSI sequence (\x1b[...) — consume until alpha terminator
|
|
127
|
+
while True:
|
|
128
|
+
seq_byte = _read_with_timeout(fd, _ESCAPE_TIMEOUT)
|
|
129
|
+
if not seq_byte or (0x40 <= seq_byte[0] <= 0x7E):
|
|
130
|
+
break
|
|
131
|
+
elif follow == b"O":
|
|
132
|
+
# SS3 sequence (\x1bO..., e.g., F1–F4) — consume terminator
|
|
133
|
+
_read_with_timeout(fd, _ESCAPE_TIMEOUT)
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
if byte == 0x03: # Ctrl+C
|
|
137
|
+
raise PromptCancelled("Ctrl+C")
|
|
138
|
+
|
|
139
|
+
if byte == 0x04: # Ctrl+D
|
|
140
|
+
raise PromptCancelled("Ctrl+D")
|
|
141
|
+
|
|
142
|
+
if byte in (0x0D, 0x0A): # Enter
|
|
143
|
+
sys.stdout.write("\n")
|
|
144
|
+
sys.stdout.flush()
|
|
145
|
+
return "".join(buf)
|
|
146
|
+
|
|
147
|
+
if byte in (0x7F, 0x08): # Backspace / Delete
|
|
148
|
+
if buf:
|
|
149
|
+
buf.pop()
|
|
150
|
+
if not password:
|
|
151
|
+
sys.stdout.write("\b \b")
|
|
152
|
+
sys.stdout.flush()
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
if byte >= 0x80: # High byte (UTF-8 lead or stray continuation)
|
|
156
|
+
char, leftover = _read_utf8_char(fd, byte)
|
|
157
|
+
if char:
|
|
158
|
+
buf.append(char)
|
|
159
|
+
if not password:
|
|
160
|
+
sys.stdout.write(char)
|
|
161
|
+
sys.stdout.flush()
|
|
162
|
+
if leftover:
|
|
163
|
+
pending = leftover
|
|
164
|
+
elif byte >= 0x20: # Printable ASCII character
|
|
165
|
+
buf.append(chr(byte))
|
|
166
|
+
if not password:
|
|
167
|
+
sys.stdout.write(chr(byte))
|
|
168
|
+
sys.stdout.flush()
|
|
169
|
+
finally:
|
|
170
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class SafePrompt(Prompt):
|
|
174
|
+
"""Rich Prompt subclass with Escape key and interrupt handling."""
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def get_input(
|
|
178
|
+
cls,
|
|
179
|
+
console, # noqa: ANN001
|
|
180
|
+
prompt, # noqa: ANN001
|
|
181
|
+
password: bool = False,
|
|
182
|
+
stream=None, # noqa: ANN001
|
|
183
|
+
) -> str:
|
|
184
|
+
if stream is not None:
|
|
185
|
+
try:
|
|
186
|
+
return super().get_input(console, prompt, password=password, stream=stream)
|
|
187
|
+
except (KeyboardInterrupt, EOFError) as exc:
|
|
188
|
+
raise PromptCancelled(str(exc)) from exc
|
|
189
|
+
|
|
190
|
+
console.print(prompt, end="")
|
|
191
|
+
try:
|
|
192
|
+
return _safe_readline(password=password)
|
|
193
|
+
except PromptCancelled:
|
|
194
|
+
console.print() # Newline after the prompt
|
|
195
|
+
raise
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class SafeConfirm(Confirm):
|
|
199
|
+
"""Rich Confirm subclass with Escape key and interrupt handling."""
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
def get_input(
|
|
203
|
+
cls,
|
|
204
|
+
console, # noqa: ANN001
|
|
205
|
+
prompt, # noqa: ANN001
|
|
206
|
+
password: bool = False,
|
|
207
|
+
stream=None, # noqa: ANN001
|
|
208
|
+
) -> str:
|
|
209
|
+
if stream is not None:
|
|
210
|
+
try:
|
|
211
|
+
return super().get_input(console, prompt, password=password, stream=stream)
|
|
212
|
+
except (KeyboardInterrupt, EOFError) as exc:
|
|
213
|
+
raise PromptCancelled(str(exc)) from exc
|
|
214
|
+
|
|
215
|
+
console.print(prompt, end="")
|
|
216
|
+
try:
|
|
217
|
+
return _safe_readline(password=password)
|
|
218
|
+
except PromptCancelled:
|
|
219
|
+
console.print() # Newline after the prompt
|
|
220
|
+
raise
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""fp api — start the HTTP API server."""
|
|
2
|
+
|
|
3
|
+
from footprinter.cli._common import FORMATTER
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _start_api(args) -> None:
|
|
7
|
+
from footprinter.api.server import main
|
|
8
|
+
|
|
9
|
+
main(host=args.host, port=args.port)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def register(subparsers) -> None:
|
|
13
|
+
"""Register the ``api`` subcommand."""
|
|
14
|
+
parser = subparsers.add_parser(
|
|
15
|
+
"api",
|
|
16
|
+
help="Start the HTTP API server",
|
|
17
|
+
description=(
|
|
18
|
+
"Start the Footprinter HTTP API server.\n\n"
|
|
19
|
+
"Provides REST endpoints for programmatic access to indexed data.\n"
|
|
20
|
+
"Auto-generated docs available at /docs (Swagger UI)."
|
|
21
|
+
),
|
|
22
|
+
epilog=(
|
|
23
|
+
"examples:\n"
|
|
24
|
+
" fp api Start on localhost:8000\n"
|
|
25
|
+
" fp api --port 9000 Start on custom port\n"
|
|
26
|
+
" fp api --host 0.0.0.0 Listen on all interfaces"
|
|
27
|
+
),
|
|
28
|
+
formatter_class=FORMATTER,
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument("--host", default="127.0.0.1", help="Host to bind (default: 127.0.0.1)")
|
|
31
|
+
parser.add_argument("--port", type=int, default=8000, help="Port to bind (default: 8000)")
|
|
32
|
+
parser.set_defaults(func=_start_api)
|