knowcode 0.1.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 (63) hide show
  1. knowcode-0.1.0.dist-info/METADATA +175 -0
  2. knowcode-0.1.0.dist-info/RECORD +63 -0
  3. knowcode-0.1.0.dist-info/WHEEL +4 -0
  4. knowcode-0.1.0.dist-info/entry_points.txt +2 -0
  5. runtime/__init__.py +4 -0
  6. runtime/artifact/__init__.py +1 -0
  7. runtime/artifact/builder.py +179 -0
  8. runtime/cli/__init__.py +1 -0
  9. runtime/cli/animation.py +278 -0
  10. runtime/cli/app.py +309 -0
  11. runtime/cli/auth.py +171 -0
  12. runtime/cli/telemetry.py +91 -0
  13. runtime/exceptions/__init__.py +1 -0
  14. runtime/exceptions/errors.py +99 -0
  15. runtime/repository/__init__.py +13 -0
  16. runtime/repository/discovery.py +64 -0
  17. runtime/repository/models.py +103 -0
  18. runtime/repository/paths.py +50 -0
  19. runtime/repository/validator.py +100 -0
  20. runtime/services/__init__.py +1 -0
  21. runtime/services/ingest_service.py +105 -0
  22. runtime/services/init_service.py +45 -0
  23. runtime/services/semantic_sync_service.py +55 -0
  24. runtime/services/status_service.py +40 -0
  25. runtime/services/sync_service.py +57 -0
  26. runtime/templates/KNOWCODE_LOADER.md.j2 +24 -0
  27. runtime/templates/README_KNOWLEDGE.md.j2 +12 -0
  28. runtime/templates/README_STRUCTURE.md.j2 +19 -0
  29. runtime/templates/__init__.py +1 -0
  30. runtime/templates/active_context.md.j2 +3 -0
  31. runtime/templates/ingest_legacy.md.j2 +15 -0
  32. runtime/templates/raw_readme.md.j2 +9 -0
  33. runtime/templates/sync_reconciliation.md.j2 +17 -0
  34. runtime/templates/synthesize_knowledge.md.j2 +32 -0
  35. runtime/templates/track_intent.md.j2 +14 -0
  36. structural_engine/__init__.py +3 -0
  37. structural_engine/diff/__init__.py +1 -0
  38. structural_engine/diff/generator.py +92 -0
  39. structural_engine/diff/models.py +48 -0
  40. structural_engine/engine.py +192 -0
  41. structural_engine/logs/__init__.py +1 -0
  42. structural_engine/logs/generator.py +33 -0
  43. structural_engine/parser/__init__.py +7 -0
  44. structural_engine/parser/discovery.py +165 -0
  45. structural_engine/parser/extractors/base.py +44 -0
  46. structural_engine/parser/languages/javascript/adapter.py +149 -0
  47. structural_engine/parser/languages/python/adapter.py +174 -0
  48. structural_engine/parser/languages/typescript/adapter.py +165 -0
  49. structural_engine/parser/models.py +186 -0
  50. structural_engine/parser/parser.py +160 -0
  51. structural_engine/parser/resolvers/calls.py +105 -0
  52. structural_engine/parser/tree_sitter/registry.py +61 -0
  53. structural_engine/reports/__init__.py +1 -0
  54. structural_engine/reports/generator.py +77 -0
  55. structural_engine/results.py +54 -0
  56. structural_engine/revisions/__init__.py +1 -0
  57. structural_engine/revisions/tracker.py +32 -0
  58. structural_engine/snapshot/__init__.py +1 -0
  59. structural_engine/snapshot/generator.py +58 -0
  60. structural_engine/snapshot/loader.py +59 -0
  61. structural_engine/state/__init__.py +1 -0
  62. structural_engine/state/manager.py +169 -0
  63. structural_engine/state/models.py +34 -0
@@ -0,0 +1,278 @@
1
+ """Logo animation module for KnowCode CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ import re
7
+ import shutil
8
+ import sys
9
+ import threading
10
+ import time
11
+
12
+ if sys.stdout.encoding != "utf-8":
13
+ try:
14
+ sys.stdout.reconfigure(encoding="utf-8")
15
+ except Exception:
16
+ pass
17
+
18
+ HIDE_CURSOR = "\033[?25l"
19
+ SHOW_CURSOR = "\033[?25h"
20
+ BOLD = "\033[1m"
21
+ DIM = "\033[2m"
22
+ RESET = "\033[0m"
23
+ WHITE = "\033[97m"
24
+ GRAY = "\033[90m"
25
+
26
+ LOGO_LINES = [
27
+ "███████████████████████████",
28
+ "██ ██",
29
+ "██ ███████████████ ██",
30
+ "██ █████▀ ▀█████ ██",
31
+ "██ █████▄ ▄█████ ██",
32
+ "██ ███████ ███████ ██",
33
+ "██ █████▀ ▀█████ ██",
34
+ "██ █████▄ ▄█████ ██",
35
+ "██ ███████████████ ██",
36
+ "██ ██",
37
+ "███████████████████████████",
38
+ ]
39
+
40
+ LOGO_HEIGHT = len(LOGO_LINES)
41
+ LOGO_WIDTH = len(LOGO_LINES[0])
42
+ LEFT_BLOCK_WIDTH = LOGO_WIDTH + 6
43
+
44
+ LXN = LOGO_WIDTH - 1
45
+ LYN = LOGO_HEIGHT - 1
46
+ TAGLINE = "KnowCode"
47
+ VERSION = "v0.1.0"
48
+
49
+
50
+ def move_up(n: int) -> None:
51
+ if n > 0:
52
+ sys.stdout.write(f"\033[{n}A")
53
+
54
+
55
+ def write(s: str) -> None:
56
+ sys.stdout.write(s)
57
+ sys.stdout.flush()
58
+
59
+
60
+ ANSI_ESCAPE = re.compile(r"\x1b\[[0-9;]*[a-zA-Z]")
61
+
62
+
63
+ def visible_len(s: str) -> int:
64
+ """Calculate the printed length of a string, excluding ANSI escape sequences."""
65
+ return len(ANSI_ESCAPE.sub("", s))
66
+
67
+
68
+ def pad_left(s: str, width: int) -> str:
69
+ """Pad a string to a specific visible width by appending spaces."""
70
+ v_len = visible_len(s)
71
+ if v_len < width:
72
+ return s + " " * (width - v_len)
73
+ return s
74
+
75
+
76
+ class BackgroundAnimator:
77
+ """Animate the KnowCode logo in the background while tasks execute."""
78
+
79
+ def __init__(self):
80
+ self.status = "Starting..."
81
+ self.running = False
82
+ self._thread = None
83
+ self.tagline_visible_chars = 0
84
+ self.connector_visible_chars = 0
85
+ self.last_height = None
86
+ self.is_tty = sys.stdout.isatty()
87
+ self.phase = 0
88
+
89
+ def start(self) -> None:
90
+ """Start the animation in a background thread if running in an interactive terminal."""
91
+ if not self.is_tty:
92
+ return
93
+
94
+ write(HIDE_CURSOR)
95
+
96
+ cols, lines = shutil.get_terminal_size()
97
+
98
+ # Reserve screen lines initially depending on terminal size
99
+ if lines < LOGO_HEIGHT + 2 or cols < 74:
100
+ self.last_height = 0
101
+ else:
102
+ write("\n" * (LOGO_HEIGHT + 1))
103
+ self.last_height = LOGO_HEIGHT + 1
104
+
105
+ self.running = True
106
+ self._thread = threading.Thread(target=self._run, daemon=True)
107
+ self._thread.start()
108
+
109
+ def stop(
110
+ self, final_status: str = "Ready", results: list[str] | None = None
111
+ ) -> None:
112
+ """Stop the animation and print the final static state."""
113
+ if not self.is_tty:
114
+ if results:
115
+ for r_line in results:
116
+ write(f"{r_line}\n")
117
+ return
118
+
119
+ if self.running:
120
+ self.status = final_status
121
+
122
+ # Enforce a minimum animation duration (up to phase 40, approx 1.4s)
123
+ # so that the circular reveal animation has time to be visible
124
+ while self.phase < 40:
125
+ time.sleep(0.05)
126
+
127
+ self.running = False
128
+ if self._thread:
129
+ self._thread.join()
130
+
131
+ # Reposition to the top of our last frame before printing final state
132
+ if self.last_height and self.last_height > 0:
133
+ move_up(self.last_height)
134
+ write("\033[J")
135
+
136
+ # Print final clean state
137
+ self._print_final(results)
138
+
139
+ write(SHOW_CURSOR)
140
+
141
+ def _get_colored_char(self, char: str, x: int, y: int, R: float) -> str:
142
+ if char == " ":
143
+ return " "
144
+
145
+ dx = (x - LXN / 2) * 0.6
146
+ dy = y - LYN / 2
147
+ d = math.sqrt(dx * dx + dy * dy)
148
+
149
+ if d > R:
150
+ return " "
151
+ elif R - 1.5 < d <= R:
152
+ theta = math.atan2(dy, dx)
153
+ hue_idx = int(((theta + math.pi) / (2 * math.pi)) * 6) % 6
154
+ colors = [
155
+ "\033[1;91m",
156
+ "\033[1;93m",
157
+ "\033[1;92m",
158
+ "\033[1;96m",
159
+ "\033[1;94m",
160
+ "\033[1;95m",
161
+ ]
162
+ return f"{colors[hue_idx]}{char}{RESET}"
163
+ else:
164
+ return f"{BOLD}{WHITE}{char}{RESET}"
165
+
166
+ def _run(self) -> None:
167
+ self.phase = 0
168
+ R_max = 12.5
169
+ cycle = 80
170
+
171
+ while self.running:
172
+ cols, lines = shutil.get_terminal_size()
173
+
174
+ # Determine display mode based on terminal size
175
+ if lines < LOGO_HEIGHT + 2 or cols < 74:
176
+ mode = "single"
177
+ current_height = 0
178
+ else:
179
+ mode = "full"
180
+ current_height = LOGO_HEIGHT + 1
181
+
182
+ # Move cursor back to the top of the previous frame
183
+ if self.last_height and self.last_height > 0:
184
+ move_up(self.last_height)
185
+ if current_height < self.last_height:
186
+ write("\033[J")
187
+
188
+ if mode == "single":
189
+ max_width = max(10, cols - 6)
190
+ status_text = self.status
191
+ if len(status_text) > max_width:
192
+ status_text = status_text[: max_width - 3] + "..."
193
+ write(f"\r ● {status_text:<{max_width}}")
194
+ else:
195
+ s = self.phase % cycle
196
+ R = R_max * (1 - math.cos(s * math.pi / 40)) / 2
197
+
198
+ left_lines = []
199
+ for y, line in enumerate(LOGO_LINES):
200
+ animated_line_chars = []
201
+ for x, char in enumerate(line):
202
+ animated_line_chars.append(
203
+ self._get_colored_char(char, x, y, R)
204
+ )
205
+ left_lines.append(f" {''.join(animated_line_chars)}")
206
+
207
+ # Tagline and active status side-by-side components on the right
208
+ right_width = cols - LEFT_BLOCK_WIDTH - 4
209
+ status_max = max(15, right_width - 2)
210
+ status_text = self.status
211
+ if len(status_text) > status_max:
212
+ status_text = status_text[: status_max - 3] + "..."
213
+
214
+ separator_len = min(31, right_width)
215
+ right_lines = [
216
+ f"{GRAY}{TAGLINE} {DIM}{GRAY}{VERSION}{RESET}",
217
+ f"{GRAY}{'─' * separator_len}{RESET}",
218
+ "",
219
+ f"{WHITE}● {status_text:<{status_max}}{RESET}",
220
+ ]
221
+
222
+ buffer = ["\n"]
223
+ max_len = max(len(left_lines), len(right_lines))
224
+ for i in range(max_len):
225
+ left = (
226
+ left_lines[i] if i < len(left_lines) else " " * LEFT_BLOCK_WIDTH
227
+ )
228
+ right = right_lines[i] if i < len(right_lines) else ""
229
+ buffer.append(f"{pad_left(left, LEFT_BLOCK_WIDTH)}{right}\n")
230
+
231
+ write("".join(buffer))
232
+
233
+ self.last_height = current_height
234
+ self.phase += 1
235
+ time.sleep(0.035)
236
+
237
+ def _print_final(self, results: list[str] | None = None) -> None:
238
+ cols, lines = shutil.get_terminal_size()
239
+
240
+ if lines < LOGO_HEIGHT + 4 or cols < 80:
241
+ max_width = max(10, cols - 6)
242
+ status_text = self.status
243
+ if len(status_text) > max_width:
244
+ status_text = status_text[: max_width - 3] + "..."
245
+ write(f"\r {WHITE}✔ {status_text:<{max_width}}{RESET}\n")
246
+ if results:
247
+ for r_line in results:
248
+ write(f"{r_line}\n")
249
+ else:
250
+ left_lines = []
251
+ for line in LOGO_LINES:
252
+ left_lines.append(f" {BOLD}{WHITE}{line}{RESET}")
253
+
254
+ right_width = cols - LEFT_BLOCK_WIDTH - 4
255
+ status_max = max(15, right_width - 2)
256
+ status_text = self.status
257
+ if len(status_text) > status_max:
258
+ status_text = status_text[: status_max - 3] + "..."
259
+
260
+ separator_len = min(31, right_width)
261
+ right_lines = [
262
+ f"{GRAY}{TAGLINE} {DIM}{GRAY}{VERSION}{RESET}",
263
+ f"{GRAY}{'─' * separator_len}{RESET}",
264
+ "",
265
+ f"{WHITE}✔ {status_text:<{status_max}}{RESET}",
266
+ "",
267
+ ]
268
+ if results:
269
+ right_lines.extend(results)
270
+
271
+ buffer = ["\n"]
272
+ max_len = max(len(left_lines), len(right_lines))
273
+ for i in range(max_len):
274
+ left = left_lines[i] if i < len(left_lines) else " " * LEFT_BLOCK_WIDTH
275
+ right = right_lines[i] if i < len(right_lines) else ""
276
+ buffer.append(f"{pad_left(left, LEFT_BLOCK_WIDTH)}{right}\n")
277
+
278
+ write("".join(buffer))
runtime/cli/app.py ADDED
@@ -0,0 +1,309 @@
1
+ """KnowCode CLI.
2
+
3
+ The top-level Typer application providing the ``know`` command.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ import structlog
12
+ import typer
13
+ from rich.console import Console
14
+ from rich.panel import Panel
15
+ from rich.table import Table
16
+
17
+ from runtime.exceptions.errors import KnowcodeError
18
+ from runtime.services.init_service import run_init
19
+ from runtime.services.status_service import run_status
20
+ from runtime.services.sync_service import run_sync
21
+ from runtime.cli.animation import BackgroundAnimator
22
+ from runtime.cli.auth import ensure_authenticated
23
+ from runtime.cli.telemetry import send_telemetry_async
24
+ import shutil
25
+
26
+ app = typer.Typer(
27
+ name="knowcode",
28
+ help="KnowCode Structural Cognition Engine",
29
+ add_completion=False,
30
+ no_args_is_help=True,
31
+ )
32
+
33
+ console = Console()
34
+ animator = BackgroundAnimator()
35
+
36
+
37
+ def _capture_console_lines(renderable) -> list[str]:
38
+ """Capture rich console output and split it into clean string lines constrained to right column."""
39
+ if sys.stdout.isatty():
40
+ cols, lines = shutil.get_terminal_size()
41
+ if cols >= 80 and lines >= 14:
42
+ right_width = max(20, cols - 35 - 4)
43
+ else:
44
+ right_width = max(20, cols - 4)
45
+ from rich.console import Console as RichConsole
46
+
47
+ temp_console = RichConsole(width=right_width, color_system=console.color_system)
48
+ else:
49
+ temp_console = console
50
+
51
+ with temp_console.capture() as capture:
52
+ temp_console.print(renderable)
53
+ return capture.get().rstrip().split("\n")
54
+
55
+
56
+ def _update_animator(logger, method_name, event_dict):
57
+ if animator and animator.running:
58
+ animator.status = str(event_dict.get("event", "Processing..."))
59
+ return event_dict
60
+
61
+
62
+ def _setup_logging() -> None:
63
+ """Configure minimal JSON logging for the entire ecosystem."""
64
+ structlog.configure(
65
+ processors=[
66
+ structlog.processors.TimeStamper(fmt="iso"),
67
+ structlog.processors.JSONRenderer(),
68
+ ],
69
+ logger_factory=structlog.PrintLoggerFactory(
70
+ file=open("knowcode.log", "a", encoding="utf-8")
71
+ ),
72
+ )
73
+
74
+
75
+ _setup_logging()
76
+
77
+
78
+ def _handle_error(e: Exception) -> None:
79
+ """Print an error nicely and exit."""
80
+ if isinstance(e, KnowcodeError):
81
+ console.print(f"[red]Error:[/red] {e}")
82
+ else:
83
+ console.print(f"[bold red]Unexpected Error:[/bold red] {e}")
84
+ sys.exit(1)
85
+
86
+
87
+ @app.command(".")
88
+ def init_command() -> None:
89
+ """Initialize a new KnowCode instance in the current repository."""
90
+ try:
91
+ ensure_authenticated()
92
+ except Exception as e:
93
+ _handle_error(e)
94
+
95
+ animator.start()
96
+ try:
97
+ cwd = Path.cwd()
98
+ result = run_init(cwd)
99
+ panel = Panel.fit(
100
+ f"[bold green]{result.message}[/bold green]\n"
101
+ f"Structural Revision: [cyan]{result.structural_revision}[/cyan]\n"
102
+ f"Initial Snapshot: [cyan]{result.snapshot_file}[/cyan]\n\n"
103
+ f"[bold yellow]Next Step:[/bold yellow] Type [magenta]/knowcode[/magenta] in your AI chat to activate governance.",
104
+ title="KnowCode Initialization",
105
+ border_style="green",
106
+ )
107
+ results_lines = _capture_console_lines(panel)
108
+ animator.stop(final_status="Initialization complete.", results=results_lines)
109
+ send_telemetry_async("init", "success")
110
+ except Exception as e:
111
+ animator.stop(final_status="Initialization failed.")
112
+ send_telemetry_async("init", "failed")
113
+ _handle_error(e)
114
+
115
+
116
+ @app.command("init")
117
+ def init_alias_command() -> None:
118
+ """Initialize a new KnowCode instance in the current repository."""
119
+ init_command()
120
+
121
+
122
+ @app.command("sync")
123
+ def sync_command() -> None:
124
+ """Synchronize the KnowCode artifact with the current repository state."""
125
+ try:
126
+ ensure_authenticated()
127
+ except Exception as e:
128
+ _handle_error(e)
129
+
130
+ animator.start()
131
+ try:
132
+ cwd = Path.cwd()
133
+ result = run_sync(cwd)
134
+
135
+ if not result.changes_detected:
136
+ results_lines = _capture_console_lines(f"[green]✓[/green] {result.message}")
137
+ animator.stop(final_status="Sync complete.", results=results_lines)
138
+ send_telemetry_async("sync", "success")
139
+ return
140
+
141
+ table = Table(title="Sync Results", show_header=False)
142
+ table.add_column("Key", style="dim")
143
+ table.add_column("Value", style="cyan")
144
+
145
+ table.add_row("Revision", result.structural_revision)
146
+ table.add_row("Snapshot", result.snapshot_file)
147
+ table.add_row("Report", result.report_file)
148
+ table.add_row(
149
+ "Components",
150
+ ", ".join(result.affected_components)
151
+ if result.affected_components
152
+ else "None",
153
+ )
154
+
155
+ panel = Panel.fit(
156
+ table, title="[bold green]Sync Complete[/bold green]", border_style="green"
157
+ )
158
+ results_lines = _capture_console_lines(panel)
159
+ animator.stop(final_status="Sync complete.", results=results_lines)
160
+ send_telemetry_async("sync", "success")
161
+
162
+ except Exception as e:
163
+ animator.stop(final_status="Sync failed.")
164
+ send_telemetry_async("sync", "failed")
165
+ _handle_error(e)
166
+
167
+
168
+ @app.command("status")
169
+ def status_command() -> None:
170
+ """View the current status of the KnowCode instance."""
171
+ try:
172
+ ensure_authenticated()
173
+ except Exception as e:
174
+ _handle_error(e)
175
+
176
+ animator.start()
177
+ try:
178
+ cwd = Path.cwd()
179
+ result = run_status(cwd)
180
+
181
+ table = Table(
182
+ title=f"KnowCode Status: {result.repository_root}", show_header=False
183
+ )
184
+ table.add_column("Key", style="dim")
185
+ table.add_column("Value", style="bold")
186
+
187
+ table.add_row(
188
+ "Initialized",
189
+ "[green]Yes[/green]" if result.initialized else "[red]No[/red]",
190
+ )
191
+ table.add_row(
192
+ "Structural Revision", f"[cyan]{result.structural_revision}[/cyan]"
193
+ )
194
+ table.add_row(
195
+ "Semantic Revision", f"[magenta]{result.semantic_revision}[/magenta]"
196
+ )
197
+ table.add_row("Current Snapshot", result.current_snapshot)
198
+ table.add_row("Latest Report", result.latest_report)
199
+
200
+ last_sync = (
201
+ result.last_sync.strftime("%Y-%m-%d %H:%M:%S UTC")
202
+ if result.last_sync
203
+ else "Never"
204
+ )
205
+ table.add_row("Last Sync", last_sync)
206
+
207
+ results_lines = _capture_console_lines(table)
208
+ animator.stop(final_status="Status retrieved.", results=results_lines)
209
+ send_telemetry_async("status", "success")
210
+
211
+ except Exception as e:
212
+ animator.stop(final_status="Failed to get status.")
213
+ send_telemetry_async("status", "failed")
214
+ _handle_error(e)
215
+
216
+
217
+ @app.command("sync-semantic")
218
+ def sync_semantic_command() -> None:
219
+ """Commit semantic changes and flush the active memory buffer."""
220
+ try:
221
+ ensure_authenticated()
222
+ except Exception as e:
223
+ _handle_error(e)
224
+
225
+ animator.start()
226
+ try:
227
+ from runtime.services.semantic_sync_service import run_semantic_sync
228
+
229
+ cwd = Path.cwd()
230
+ result = run_semantic_sync(cwd)
231
+
232
+ table = Table(title="Semantic Sync Results", show_header=False)
233
+ table.add_column("Key", style="dim")
234
+ table.add_column("Value", style="magenta")
235
+
236
+ table.add_row("Revision", result.semantic_revision)
237
+ table.add_row("Memory", "[dim]Flushed previous_context.md[/dim]")
238
+
239
+ panel = Panel.fit(
240
+ table,
241
+ title=f"[bold magenta]✓ {result.message}[/bold magenta]",
242
+ border_style="magenta",
243
+ )
244
+ results_lines = _capture_console_lines(panel)
245
+ animator.stop(final_status="Semantic sync complete.", results=results_lines)
246
+ send_telemetry_async("sync-semantic", "success")
247
+
248
+ except Exception as e:
249
+ animator.stop(final_status="Semantic sync failed.")
250
+ send_telemetry_async("sync-semantic", "failed")
251
+ _handle_error(e)
252
+
253
+
254
+ @app.command("ingest-semantic")
255
+ def ingest_semantic_command(
256
+ target_file: str = typer.Argument(
257
+ ..., help="Path to the raw file to ingest and remove, or '.' to process all."
258
+ ),
259
+ ) -> None:
260
+ """Ingest a legacy document and bump the semantic revision."""
261
+ animator.start()
262
+ try:
263
+ from runtime.services.ingest_service import run_ingest
264
+
265
+ cwd = Path.cwd()
266
+ result = run_ingest(cwd, target_file)
267
+ animator.stop(final_status="Semantic ingest complete.")
268
+
269
+ if result.semantic_revision == "No change":
270
+ console.print(f"[yellow]ℹ {result.message}[/yellow]")
271
+ return
272
+
273
+ table = Table(title="Ingest Results", show_header=False)
274
+ table.add_column("Key", style="dim")
275
+ table.add_column("Value", style="magenta")
276
+
277
+ table.add_row("Revision", result.semantic_revision)
278
+ files_str = (
279
+ ", ".join(result.deleted_files)
280
+ if len(result.deleted_files) <= 3
281
+ else f"{len(result.deleted_files)} files"
282
+ )
283
+ table.add_row("Memory", f"[dim]Flushed {files_str} from inbox[/dim]")
284
+
285
+ console.print(
286
+ Panel.fit(
287
+ table,
288
+ title=f"[bold magenta]✓ {result.message}[/bold magenta]",
289
+ border_style="magenta",
290
+ )
291
+ )
292
+
293
+ except Exception as e:
294
+ animator.stop(final_status="Semantic ingest failed.")
295
+ _handle_error(e)
296
+
297
+
298
+ @app.command("auth")
299
+ def auth_command() -> None:
300
+ """Manage authentication and telemetry preferences."""
301
+ from runtime.cli.auth import manage_auth
302
+ try:
303
+ manage_auth()
304
+ except Exception as e:
305
+ console.print(f"[bold red]Error:[/bold red] {e}")
306
+
307
+
308
+ if __name__ == "__main__":
309
+ app()