@@ -0,0 +1,378 @@
1 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Interactive planning companion REPL for PlanOpticon."""
2 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
3 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import logging
4 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from pathlib import Path
5 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from typing import List, Optional
6 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
7 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger = logging.getLogger(__name__)
8 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
9 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ VIDEO_EXTS = {".mp4", ".mkv", ".webm"}
10 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ DOC_EXTS = {".md", ".pdf", ".docx"}
11 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
12 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
13 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ class CompanionREPL:
14 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Smart REPL with workspace awareness and KG querying."""
15 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
16 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def __init__(
17 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self,
18 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ kb_paths: Optional[List[str]] = None,
19 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ provider: str = "auto",
20 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ chat_model: Optional[str] = None,
21 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ):
22 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.kg = None
23 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.query_engine = None
24 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.agent = None
25 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.provider_manager = None
26 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._kb_paths = kb_paths or []
27 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._provider_name = provider
28 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._chat_model = chat_model
29 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._videos: List[Path] = []
30 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._docs: List[Path] = []
31 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._kg_path: Optional[Path] = None
32 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
33 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _discover(self) -> None:
34 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Auto-discover workspace context."""
35 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Discover knowledge graphs
36 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from video_processor.integrators.graph_discovery import (
37 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ find_nearest_graph,
38 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
39 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
40 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if self._kb_paths:
41 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Use explicit paths
42 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._kg_path = Path(self._kb_paths[0])
43 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ else:
44 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._kg_path = find_nearest_graph()
45 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
46 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if self._kg_path and self._kg_path.exists():
47 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._load_kg(self._kg_path)
48 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
49 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # Scan for media and doc files in cwd
50 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ cwd = Path.cwd()
51 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
52 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for f in sorted(cwd.iterdir()):
53 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if f.suffix.lower() in VIDEO_EXTS:
54 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._videos.append(f)
55 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ elif f.suffix.lower() in DOC_EXTS:
56 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._docs.append(f)
57 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except PermissionError:
58 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ pass
59 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
60 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _load_kg(self, path: Path) -> None:
61 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Load a knowledge graph from a file path."""
62 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from video_processor.integrators.graph_query import (
63 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ GraphQueryEngine,
64 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
65 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
66 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
67 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if path.suffix == ".json":
68 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.query_engine = GraphQueryEngine.from_json_path(path)
69 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ else:
70 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.query_engine = GraphQueryEngine.from_db_path(path)
71 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.kg = self.query_engine.store
72 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except Exception as exc:
73 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger.debug("Failed to load KG at %s: %s", path, exc)
74 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
75 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _init_provider(self) -> None:
76 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Try to initialise an LLM provider."""
77 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
78 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from video_processor.providers.manager import (
79 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ProviderManager,
80 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
81 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
82 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ prov = None if self._provider_name == "auto" else self._provider_name
83 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.provider_manager = ProviderManager(
84 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ chat_model=self._chat_model,
85 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ provider=prov,
86 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
87 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except Exception:
88 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.provider_manager = None
89 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
90 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _init_agent(self) -> None:
91 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Create a PlanningAgent if possible."""
92 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
93 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from video_processor.agent.agent_loop import (
94 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ PlanningAgent,
95 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
96 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from video_processor.agent.skills.base import (
97 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ AgentContext,
98 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
99 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
100 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ctx = AgentContext(
101 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ knowledge_graph=self.kg,
102 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ query_engine=self.query_engine,
103 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ provider_manager=self.provider_manager,
104 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
105 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.agent = PlanningAgent(context=ctx)
106 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except Exception:
107 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.agent = None
108 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
109 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _welcome_banner(self) -> str:
110 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Build the welcome banner text."""
111 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines = [
112 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "",
113 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " PlanOpticon Companion",
114 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " Interactive planning REPL",
115 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "",
116 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ]
117 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
118 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if self._kg_path and self.query_engine:
119 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ stats = self.query_engine.stats().data
120 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append(
121 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f" Knowledge graph: {self._kg_path.name}"
122 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f" ({stats['entity_count']} entities,"
123 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f" {stats['relationship_count']} relationships)"
124 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
125 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ else:
126 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append(" No knowledge graph loaded.")
127 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
128 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if self._videos:
129 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ names = ", ".join(v.name for v in self._videos[:3])
130 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ suffix = f" (+{len(self._videos) - 3} more)" if len(self._videos) > 3 else ""
131 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append(f" Videos: {names}{suffix}")
132 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
133 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if self._docs:
134 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ names = ", ".join(d.name for d in self._docs[:3])
135 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ suffix = f" (+{len(self._docs) - 3} more)" if len(self._docs) > 3 else ""
136 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append(f" Docs: provider_label = "active" if
137 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
138 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _discover(selelse "none"
139 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ext."
140 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
141 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.appider_label}ne")
142 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.appendne")
143 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append("")
144 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append(" Type /help for commands, or ask a question.")
145 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append("")
146 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "\n".join(lines)
147 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
148 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # ── Command handlers ──
149 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
150 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _cmd_help(self) -> str:
151 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines = [
152 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "Available commands:",
153 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /help Show this help",
154 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /status Workspace status",
155 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /skills List available skills",
156 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /entities [--type T] List KG entities",
157 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /search TERM Search entities by name",
158 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /neighbors ENTITY Show entity relationships",
159 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /export FORMAT Export KG (markdown, obsidian, notion, csv)",
160 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /analyze PATH Analyze a video/doc",
161 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /ingest PATH Ingest a file intrun SKILL Run a skill by name",
162 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /plan Run project_plan skill",
163 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /prd Run PRD skill",
164 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /tasks Run task_breakdown skill",
165 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " /quit, /exit Exit companion",
166 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "",
167 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "Any other input is sent to the chat agent (requires LLM).",
168 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ]
169 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "\n".join(lines)
170 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
171 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _cmd_status(self) -> str:
172 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines = ["Workspace status:"]
173 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if self._kg_path and self.query_engine:
174 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ stats = self.query_engine.stats().data
175 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append(
176 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f" KG: {self._kg_path}"
177 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f" ({stats['entity_count']} entities,"
178 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f" {stats['relationship_count']} relationships)"
179 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
180 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if stats.get("entity_types"):
181 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for t, c in sorted(
182 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ stats["entity_types"].items(),
183 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ key=lambda x: -x[1],
184 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ):
185 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append(f" {t}: {c}")
186 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ else:
187 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append(" KG: not loaded")
188 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
189 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append(f" Videos: {len(self._videos)} found")
190 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append(f" Docs: {len(self._docs)} found")
191 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append(f" Provider: {'active' if self.provider_manager else 'none'}")
192 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "\n".join(lines)
193 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
194 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _cmd_skills(self) -> str:
195 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from video_processor.agent.skills.base import (
196 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ list_skills,
197 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
198 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
199 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ skills = list_skills()
200 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not skills:
201 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "No skills registered."
202 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines = ["Available skills:"]
203 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for s in skills:
204 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ lines.append(f" {s.name}: {s.description}")
205 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "\n".join(lines)
206 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
207 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _cmd_entities(self, args: str) -> str:
208 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not self.query_engine:
209 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "No knowledge graph loaded."
210 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ entity_type = None
211 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ parts = args.split()
212 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ for i, part in enumerate(parts):
213 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if part == "--type" and i + 1 < len(parts):
214 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ entity_type = parts[i + 1]
215 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ result = self.query_engine.entities(
216 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ entity_type=entity_type,
217 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
218 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return result.to_text()
219 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
220 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _cmd_search(self, term: str) -> str:
221 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not self.query_engine:
222 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "No knowledge graph loaded."
223 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ term = term.strip()
224 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not term:
225 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "Usage: /search TERM"
226 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ result = self.query_engine.entities(name=term)
227 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return result.to_text()
228 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
229 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _cmd_neighbors(self, entity: str) -> str:
230 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not self.query_engine:
231 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "No knowledge graph loaded."
232 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ entity = entity.strip()
233 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not entity:
234 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "Usage: /neighbors ENTITY"
235 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ result = self.query_engine.neighbors(entity)
236 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return result.to_text()
237 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
238 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _cmd_export(self, fmt: str) -> str:
239 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ fmt = fmt.strip().lower()
240 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not fmt:
241 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "Usage: /export FORMAT (markdown, obsidian, notion, csv)"
242 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not self._kg_path:
243 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "No knowledge graph loaded."
244 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return (
245 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f"Export '{fmt}' requested. Use the CLI command:\n"
246 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ f" planopticon export {fmt} {self._kg_path}"
247 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
248 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
249 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _cmd_analyze(self, path_str: str) -> str:
250 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ path_str = path_str.strip()
251 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not path_str:
252 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "Usage: /analyze PATH"
253 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ p = Path(path_str)
254 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not p.exists():
255 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return f"File not found: {path_str}"
256 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return f"Analyze requested for {p.name}. Use the CLI:\n planopticon analyze -i {p}"
257 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
258 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _cmd_ingest(self, path_str: str) -> str:
259 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ path_str = path_str.strip()
260 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not path_str:
261 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "Usage: /ingest PATH"
262 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ p = Path(path_str)
263 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not p.exists():
264 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return f"File not found: {path_str}"
265 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return f"Ingest requested for {p.name}. Use the CLI:\n planopticon ingest {p}"
266 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
267 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _cmd_run_skill(self, skill_name: str) -> str:
268 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ skill_name = skill_name.strip()
269 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not skill_name:
270 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "Usage: /run SKILL_NAME"
271 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from video_processor.agent.skills.base import (
272 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ get_skill,
273 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
274 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
275 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ skill = get_skill(skill_name)
276 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not skill:
277 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return f"Unknown skill: {skill_name}"
278 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not self.agent:
279 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return "Agent not initialised (no LLM provider?)."
280 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if not skill.can_execute(self.agent.context):
281 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return f"Skill '{skill_name}' cannot execute in current context."
282 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
283 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ artifact = skill.execute(self.agent.context)
284 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return f"--- {artifact.name} ({artifact.artifact_type}) ---\n{artifact.content}"
285 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except Exception as exc:
286 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return f"Skill execution fa return (
287 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "Chat requires an LLM provider. Set one of:\n"
288 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " OPENAI_API_KEY\n"
289 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " ANTHROPIC_API_KEY\n"
290 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ " GEMINI_API_KEY\n"
291 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ "Or pass --provider / --chat-model."
292 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ )
293 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ try:
294 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return self.agent.chat(message)
295 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ except Exception as exc:
296 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return f"Chat error: {exc}"
297 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
298 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ # ── Main dispatch ──
299 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
300 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def handle_input(self, line: str) -> str:
301 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Process a single def run find_nearest_graph,
302 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Main REPL loop."""
303 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._discover()
304 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._init_provider()
305 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
306 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ xcept (KeyboardInterrupt, EOFError):
307 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ print("\nBye.")
308 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ break
309 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
310 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ output = self.handle_input(line)
311 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if output == "__QUIT__":
312 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ print("Bye.")
313 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ break
314 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if output:
315 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ print(output)
316 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Interactive planning companion REPL for PlanOpticon."""
317 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
318 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import logging
319 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from pathlib import Path
320 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from typing import List, Optional
321 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
322 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger = logging.getLogger(__name__)
323 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
324 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ VIDEO_EXTS = {".mp4", ".mkv", ".webm"}
325 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ DOC_EXTS = {".md", ".pdf", ".docx"}
326 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
327 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
328 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ class CompanionREPL:
329 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Smart REPL with workspace awareness and KG querying."""
330 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
331 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def __init__(
332 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self,
333 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ kb_paths: Optional[List[str]] = None,
334 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ provider: str = "auto",
335 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ chat_model: Optional[str] = None,
336 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ):
337 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.kg = None
338 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.query_engine = None
339 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.agent = None
340 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.provider_manager = None
341 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._kb_paths = kb_paths or []
342 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._provider_name = provider
343 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._chat_model = chat_model
344 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._videos: List[Path] = []
345 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._docs: List[Path] = []
346 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._kg_path: Optional[Path] = None
347 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
348 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def _discover(self) -> None:
349 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Auto-discover worklanning companion REPL for PlanOpticon."""
350 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
351 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ import logging
352 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from pathlib import Path
353 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ from typing import List, Optional
354 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
355 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ logger = logging.getLogger(__name__)
356 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
357 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ VIDEO_EXTS = {".mp4", ".mkv", ".webm"}
358 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ DOC_EXTS = {".md", ".pdf", ".docx"}
359 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
360 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
361 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ class CompanionREPL:
362 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ """Smart REPL with workspace awareness and KG querying."""
363 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
364 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ def __init__(
365 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self,
366 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ kb_paths: Optional[List[str]] = None,
367 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ provider: str = "auto",
368 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ chat_model: Optional[str] = None,
369 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ ):
370 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.kg = None
371 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.query_engine = None
372 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.agent = None
373 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self.provider_manager = None
374 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._kb_paths = kb_paths or []
375 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._provider_name = provider
376 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._chat_model = chat_model
377 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._videos: List[Path] = []
378 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ self._docs: List[Path]