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.
Files changed (134) hide show
  1. footprinter/__init__.py +8 -0
  2. footprinter/access.py +444 -0
  3. footprinter/api/__init__.py +1 -0
  4. footprinter/api/db.py +61 -0
  5. footprinter/api/entities.py +250 -0
  6. footprinter/api/search.py +47 -0
  7. footprinter/api/semantic.py +33 -0
  8. footprinter/api/server.py +66 -0
  9. footprinter/api/status.py +15 -0
  10. footprinter/bundled/__init__.py +0 -0
  11. footprinter/bundled/config.example.yaml +161 -0
  12. footprinter/bundled/patterns/context_patterns.yaml +18 -0
  13. footprinter/bundled/patterns/extensions.yaml +283 -0
  14. footprinter/bundled/patterns/filename_patterns.yaml +61 -0
  15. footprinter/bundled/patterns/mime_mappings.yaml +68 -0
  16. footprinter/bundled/patterns/salesforce_rules.yaml +84 -0
  17. footprinter/bundled/patterns/security_patterns.yaml +27 -0
  18. footprinter/cli/__init__.py +128 -0
  19. footprinter/cli/__main__.py +6 -0
  20. footprinter/cli/_common.py +332 -0
  21. footprinter/cli/_policy_helpers.py +646 -0
  22. footprinter/cli/_prompt.py +220 -0
  23. footprinter/cli/api_cmd.py +32 -0
  24. footprinter/cli/connect.py +591 -0
  25. footprinter/cli/data.py +879 -0
  26. footprinter/cli/delete.py +128 -0
  27. footprinter/cli/ingest.py +579 -0
  28. footprinter/cli/mcp_cmd.py +750 -0
  29. footprinter/cli/mcp_setup.py +306 -0
  30. footprinter/cli/search.py +393 -0
  31. footprinter/cli/search_cmd.py +69 -0
  32. footprinter/cli/setup.py +1836 -0
  33. footprinter/cli/status.py +729 -0
  34. footprinter/cli/status_cmd.py +104 -0
  35. footprinter/cli/upsert.py +794 -0
  36. footprinter/cli/vectorize_cmd.py +215 -0
  37. footprinter/cli/view.py +322 -0
  38. footprinter/connectors/__init__.py +171 -0
  39. footprinter/connectors/config_utils.py +141 -0
  40. footprinter/db/__init__.py +37 -0
  41. footprinter/db/browser.py +198 -0
  42. footprinter/db/chats.py +610 -0
  43. footprinter/db/clients.py +307 -0
  44. footprinter/db/emails.py +279 -0
  45. footprinter/db/files.py +741 -0
  46. footprinter/db/folders.py +659 -0
  47. footprinter/db/messages.py +192 -0
  48. footprinter/db/policies.py +151 -0
  49. footprinter/db/projects.py +673 -0
  50. footprinter/db/search.py +573 -0
  51. footprinter/db/sql_utils.py +168 -0
  52. footprinter/db/status.py +320 -0
  53. footprinter/db/uploads.py +70 -0
  54. footprinter/ingest/__init__.py +0 -0
  55. footprinter/ingest/adapters/__init__.py +33 -0
  56. footprinter/ingest/adapters/browser.py +54 -0
  57. footprinter/ingest/adapters/chat.py +57 -0
  58. footprinter/ingest/adapters/ingest.py +146 -0
  59. footprinter/ingest/adapters/local_files.py +68 -0
  60. footprinter/ingest/adapters/local_folders.py +52 -0
  61. footprinter/ingest/adapters/protocol.py +174 -0
  62. footprinter/ingest/browser_indexer.py +216 -0
  63. footprinter/ingest/chat_dedup.py +156 -0
  64. footprinter/ingest/chat_indexer.py +515 -0
  65. footprinter/ingest/chat_parsers/__init__.py +8 -0
  66. footprinter/ingest/chat_parsers/chatgpt_parser.py +229 -0
  67. footprinter/ingest/chat_parsers/claude_parser.py +161 -0
  68. footprinter/ingest/cli.py +827 -0
  69. footprinter/ingest/content_extractors.py +117 -0
  70. footprinter/ingest/database.py +36 -0
  71. footprinter/ingest/db/__init__.py +1 -0
  72. footprinter/ingest/db/connector_schema.py +47 -0
  73. footprinter/ingest/db/migration.py +328 -0
  74. footprinter/ingest/db/schema.py +1043 -0
  75. footprinter/ingest/db/security.py +6 -0
  76. footprinter/ingest/file_indexer.py +261 -0
  77. footprinter/ingest/file_scanner.py +277 -0
  78. footprinter/ingest/folder_indexer.py +226 -0
  79. footprinter/ingest/full_content_extractor.py +321 -0
  80. footprinter/ingest/orchestrator.py +125 -0
  81. footprinter/ingest/pipe_runner.py +217 -0
  82. footprinter/ingest/processing.py +165 -0
  83. footprinter/ingest/registry.py +201 -0
  84. footprinter/ingest/run_record.py +91 -0
  85. footprinter/ingest/status.py +346 -0
  86. footprinter/mcp/__init__.py +0 -0
  87. footprinter/mcp/__main__.py +5 -0
  88. footprinter/mcp/db.py +57 -0
  89. footprinter/mcp/errors.py +102 -0
  90. footprinter/mcp/extraction.py +226 -0
  91. footprinter/mcp/server.py +39 -0
  92. footprinter/mcp/tools/__init__.py +0 -0
  93. footprinter/mcp/tools/navigation.py +70 -0
  94. footprinter/mcp/tools/read.py +75 -0
  95. footprinter/mcp/tools/search.py +158 -0
  96. footprinter/mcp/tools/semantic.py +79 -0
  97. footprinter/mcp/tools/status.py +15 -0
  98. footprinter/paths.py +91 -0
  99. footprinter/permissions.py +1160 -0
  100. footprinter/semantic/__init__.py +13 -0
  101. footprinter/semantic/chunking.py +52 -0
  102. footprinter/semantic/embeddings.py +23 -0
  103. footprinter/semantic/hybrid_search.py +273 -0
  104. footprinter/semantic/vector_store.py +471 -0
  105. footprinter/services/__init__.py +49 -0
  106. footprinter/services/access_service.py +342 -0
  107. footprinter/services/chat_service.py +85 -0
  108. footprinter/services/client_service.py +267 -0
  109. footprinter/services/content_service.py +181 -0
  110. footprinter/services/email_service.py +89 -0
  111. footprinter/services/file_service.py +83 -0
  112. footprinter/services/folder_service.py +122 -0
  113. footprinter/services/includes.py +19 -0
  114. footprinter/services/ingest_service.py +231 -0
  115. footprinter/services/project_service.py +262 -0
  116. footprinter/services/roles.py +25 -0
  117. footprinter/services/search_service.py +177 -0
  118. footprinter/services/semantic_service.py +360 -0
  119. footprinter/services/status_service.py +18 -0
  120. footprinter/services/visit_service.py +65 -0
  121. footprinter/source_registry.py +194 -0
  122. footprinter/utils/__init__.py +7 -0
  123. footprinter/utils/hash_utils.py +59 -0
  124. footprinter/utils/logging_config.py +68 -0
  125. footprinter/utils/mime.py +30 -0
  126. footprinter/utils/text.py +6 -0
  127. footprinter/utils/time.py +11 -0
  128. footprinter/visibility.py +1272 -0
  129. footprinter_cli-1.0.0.dist-info/LICENSE +21 -0
  130. footprinter_cli-1.0.0.dist-info/METADATA +229 -0
  131. footprinter_cli-1.0.0.dist-info/RECORD +134 -0
  132. footprinter_cli-1.0.0.dist-info/WHEEL +5 -0
  133. footprinter_cli-1.0.0.dist-info/entry_points.txt +2 -0
  134. 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)