praisonai-code 0.0.1__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.
- praisonai_code/__init__.py +17 -0
- praisonai_code/cli/__init__.py +12 -0
- praisonai_code/cli/_forward_shim.py +10 -0
- praisonai_code/cli/_paths.py +88 -0
- praisonai_code/cli/_warnings.py +58 -0
- praisonai_code/cli/app.py +757 -0
- praisonai_code/cli/approval_backend.py +272 -0
- praisonai_code/cli/branding.py +94 -0
- praisonai_code/cli/commands/__init__.py +114 -0
- praisonai_code/cli/commands/acp.py +80 -0
- praisonai_code/cli/commands/agent.py +116 -0
- praisonai_code/cli/commands/agents.py +80 -0
- praisonai_code/cli/commands/app.py +139 -0
- praisonai_code/cli/commands/attach.py +95 -0
- praisonai_code/cli/commands/audit.py +102 -0
- praisonai_code/cli/commands/auth.py +508 -0
- praisonai_code/cli/commands/batch.py +848 -0
- praisonai_code/cli/commands/benchmark.py +286 -0
- praisonai_code/cli/commands/browser.py +299 -0
- praisonai_code/cli/commands/call.py +45 -0
- praisonai_code/cli/commands/chat.py +332 -0
- praisonai_code/cli/commands/checkpoint.py +170 -0
- praisonai_code/cli/commands/code.py +276 -0
- praisonai_code/cli/commands/command.py +114 -0
- praisonai_code/cli/commands/commit.py +47 -0
- praisonai_code/cli/commands/completion.py +333 -0
- praisonai_code/cli/commands/config.py +681 -0
- praisonai_code/cli/commands/context.py +414 -0
- praisonai_code/cli/commands/daemon.py +203 -0
- praisonai_code/cli/commands/debug.py +142 -0
- praisonai_code/cli/commands/deploy.py +71 -0
- praisonai_code/cli/commands/diag.py +55 -0
- praisonai_code/cli/commands/docs.py +1575 -0
- praisonai_code/cli/commands/doctor.py +332 -0
- praisonai_code/cli/commands/endpoints.py +51 -0
- praisonai_code/cli/commands/environment.py +179 -0
- praisonai_code/cli/commands/eval.py +131 -0
- praisonai_code/cli/commands/examples.py +953 -0
- praisonai_code/cli/commands/flow.py +436 -0
- praisonai_code/cli/commands/github.py +752 -0
- praisonai_code/cli/commands/hooks.py +74 -0
- praisonai_code/cli/commands/init.py +174 -0
- praisonai_code/cli/commands/knowledge.py +440 -0
- praisonai_code/cli/commands/langextract.py +120 -0
- praisonai_code/cli/commands/langfuse.py +984 -0
- praisonai_code/cli/commands/loop.py +211 -0
- praisonai_code/cli/commands/lsp.py +112 -0
- praisonai_code/cli/commands/managed.py +659 -0
- praisonai_code/cli/commands/mcp.py +763 -0
- praisonai_code/cli/commands/memory.py +298 -0
- praisonai_code/cli/commands/models.py +264 -0
- praisonai_code/cli/commands/n8n.py +326 -0
- praisonai_code/cli/commands/obs.py +19 -0
- praisonai_code/cli/commands/package.py +76 -0
- praisonai_code/cli/commands/paths.py +106 -0
- praisonai_code/cli/commands/permissions.py +272 -0
- praisonai_code/cli/commands/plugins.py +609 -0
- praisonai_code/cli/commands/port.py +530 -0
- praisonai_code/cli/commands/profile.py +466 -0
- praisonai_code/cli/commands/publish.py +193 -0
- praisonai_code/cli/commands/rag.py +913 -0
- praisonai_code/cli/commands/realtime.py +52 -0
- praisonai_code/cli/commands/recipe.py +684 -0
- praisonai_code/cli/commands/registry.py +59 -0
- praisonai_code/cli/commands/replay.py +830 -0
- praisonai_code/cli/commands/research.py +49 -0
- praisonai_code/cli/commands/retrieval.py +377 -0
- praisonai_code/cli/commands/rules.py +71 -0
- praisonai_code/cli/commands/run.py +1573 -0
- praisonai_code/cli/commands/sandbox.py +371 -0
- praisonai_code/cli/commands/schedule.py +529 -0
- praisonai_code/cli/commands/serve.py +690 -0
- praisonai_code/cli/commands/session.py +450 -0
- praisonai_code/cli/commands/setup.py +174 -0
- praisonai_code/cli/commands/skills.py +545 -0
- praisonai_code/cli/commands/standardise.py +711 -0
- praisonai_code/cli/commands/templates.py +54 -0
- praisonai_code/cli/commands/test.py +558 -0
- praisonai_code/cli/commands/todo.py +74 -0
- praisonai_code/cli/commands/tools.py +205 -0
- praisonai_code/cli/commands/traces.py +145 -0
- praisonai_code/cli/commands/tracker.py +852 -0
- praisonai_code/cli/commands/train.py +613 -0
- praisonai_code/cli/commands/ui.py +172 -0
- praisonai_code/cli/commands/up.py +354 -0
- praisonai_code/cli/commands/validate.py +291 -0
- praisonai_code/cli/commands/version.py +101 -0
- praisonai_code/cli/commands/workflow.py +97 -0
- praisonai_code/cli/config_loader.py +437 -0
- praisonai_code/cli/configuration/__init__.py +27 -0
- praisonai_code/cli/configuration/config.schema.json +57 -0
- praisonai_code/cli/configuration/credentials.py +446 -0
- praisonai_code/cli/configuration/loader.py +364 -0
- praisonai_code/cli/configuration/model_resolver.py +161 -0
- praisonai_code/cli/configuration/oauth.py +389 -0
- praisonai_code/cli/configuration/paths.py +224 -0
- praisonai_code/cli/configuration/resolver.py +687 -0
- praisonai_code/cli/configuration/schema.py +317 -0
- praisonai_code/cli/execution/__init__.py +99 -0
- praisonai_code/cli/execution/core.py +208 -0
- praisonai_code/cli/execution/profiler.py +898 -0
- praisonai_code/cli/execution/request.py +85 -0
- praisonai_code/cli/execution/result.py +74 -0
- praisonai_code/cli/fallback_schema.py +416 -0
- praisonai_code/cli/features/__init__.py +278 -0
- praisonai_code/cli/features/_endpoint_registry.py +64 -0
- praisonai_code/cli/features/_search_registry.py +43 -0
- praisonai_code/cli/features/acp.py +236 -0
- praisonai_code/cli/features/action_orchestrator.py +576 -0
- praisonai_code/cli/features/agent_scheduler.py +773 -0
- praisonai_code/cli/features/agent_tools.py +603 -0
- praisonai_code/cli/features/agents.py +397 -0
- praisonai_code/cli/features/at_mentions.py +471 -0
- praisonai_code/cli/features/audit_cli.py +270 -0
- praisonai_code/cli/features/auto_memory.py +182 -0
- praisonai_code/cli/features/auto_mode.py +552 -0
- praisonai_code/cli/features/autonomy_mode.py +546 -0
- praisonai_code/cli/features/background.py +356 -0
- praisonai_code/cli/features/base.py +168 -0
- praisonai_code/cli/features/benchmark.py +1462 -0
- praisonai_code/cli/features/capabilities.py +1326 -0
- praisonai_code/cli/features/checkpoints.py +345 -0
- praisonai_code/cli/features/cli_profiler.py +335 -0
- praisonai_code/cli/features/code_intelligence.py +666 -0
- praisonai_code/cli/features/compaction.py +294 -0
- praisonai_code/cli/features/compare.py +534 -0
- praisonai_code/cli/features/config_hierarchy.py +366 -0
- praisonai_code/cli/features/context_manager.py +597 -0
- praisonai_code/cli/features/cost_tracker.py +514 -0
- praisonai_code/cli/features/csv_test_runner.py +736 -0
- praisonai_code/cli/features/custom_definitions.py +790 -0
- praisonai_code/cli/features/debug.py +810 -0
- praisonai_code/cli/features/deploy.py +605 -0
- praisonai_code/cli/features/diag.py +289 -0
- praisonai_code/cli/features/display_jsonl.py +173 -0
- praisonai_code/cli/features/doctor/__init__.py +63 -0
- praisonai_code/cli/features/doctor/checks/__init__.py +29 -0
- praisonai_code/cli/features/doctor/checks/acp_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/bot_checks.py +340 -0
- praisonai_code/cli/features/doctor/checks/config_checks.py +373 -0
- praisonai_code/cli/features/doctor/checks/db_checks.py +366 -0
- praisonai_code/cli/features/doctor/checks/env_checks.py +637 -0
- praisonai_code/cli/features/doctor/checks/gateway_checks.py +387 -0
- praisonai_code/cli/features/doctor/checks/lsp_checks.py +231 -0
- praisonai_code/cli/features/doctor/checks/mcp_checks.py +367 -0
- praisonai_code/cli/features/doctor/checks/memory_checks.py +268 -0
- praisonai_code/cli/features/doctor/checks/network_checks.py +251 -0
- praisonai_code/cli/features/doctor/checks/obs_checks.py +328 -0
- praisonai_code/cli/features/doctor/checks/packaging_checks.py +422 -0
- praisonai_code/cli/features/doctor/checks/performance_checks.py +235 -0
- praisonai_code/cli/features/doctor/checks/permissions_checks.py +259 -0
- praisonai_code/cli/features/doctor/checks/runtime_checks.py +650 -0
- praisonai_code/cli/features/doctor/checks/runtime_migration_checks.py +220 -0
- praisonai_code/cli/features/doctor/checks/selftest_checks.py +322 -0
- praisonai_code/cli/features/doctor/checks/serve_checks.py +426 -0
- praisonai_code/cli/features/doctor/checks/skills_checks.py +327 -0
- praisonai_code/cli/features/doctor/checks/tools_checks.py +371 -0
- praisonai_code/cli/features/doctor/engine.py +266 -0
- praisonai_code/cli/features/doctor/formatters.py +377 -0
- praisonai_code/cli/features/doctor/handler.py +564 -0
- praisonai_code/cli/features/doctor/models.py +276 -0
- praisonai_code/cli/features/doctor/registry.py +239 -0
- praisonai_code/cli/features/endpoints.py +1016 -0
- praisonai_code/cli/features/eval.py +559 -0
- praisonai_code/cli/features/examples.py +707 -0
- praisonai_code/cli/features/external_agents.py +231 -0
- praisonai_code/cli/features/fast_context.py +410 -0
- praisonai_code/cli/features/file_history.py +320 -0
- praisonai_code/cli/features/flow_display.py +566 -0
- praisonai_code/cli/features/git_attribution.py +159 -0
- praisonai_code/cli/features/git_integration.py +651 -0
- praisonai_code/cli/features/guardrail.py +171 -0
- praisonai_code/cli/features/handoff.py +252 -0
- praisonai_code/cli/features/hooks.py +583 -0
- praisonai_code/cli/features/hybrid_workflow.py +391 -0
- praisonai_code/cli/features/image.py +384 -0
- praisonai_code/cli/features/interactive_core_headless.py +450 -0
- praisonai_code/cli/features/interactive_runtime.py +600 -0
- praisonai_code/cli/features/interactive_test_harness.py +537 -0
- praisonai_code/cli/features/interactive_tools.py +428 -0
- praisonai_code/cli/features/interactive_tui.py +603 -0
- praisonai_code/cli/features/job_workflow.py +906 -0
- praisonai_code/cli/features/jobs.py +632 -0
- praisonai_code/cli/features/knowledge.py +531 -0
- praisonai_code/cli/features/knowledge_cli.py +438 -0
- praisonai_code/cli/features/lite.py +244 -0
- praisonai_code/cli/features/logs.py +200 -0
- praisonai_code/cli/features/lsp_cli.py +225 -0
- praisonai_code/cli/features/lsp_diagnostics.py +185 -0
- praisonai_code/cli/features/mcp.py +344 -0
- praisonai_code/cli/features/message_queue.py +587 -0
- praisonai_code/cli/features/metrics.py +210 -0
- praisonai_code/cli/features/migrate.py +1329 -0
- praisonai_code/cli/features/migration_flow.py +463 -0
- praisonai_code/cli/features/migration_spec.py +276 -0
- praisonai_code/cli/features/n8n.py +703 -0
- praisonai_code/cli/features/observability.py +293 -0
- praisonai_code/cli/features/ollama.py +361 -0
- praisonai_code/cli/features/output_modes.py +155 -0
- praisonai_code/cli/features/output_style.py +273 -0
- praisonai_code/cli/features/package.py +631 -0
- praisonai_code/cli/features/performance.py +308 -0
- praisonai_code/cli/features/persistence.py +636 -0
- praisonai_code/cli/features/profiler/__init__.py +81 -0
- praisonai_code/cli/features/profiler/core.py +558 -0
- praisonai_code/cli/features/profiler/optimizations.py +652 -0
- praisonai_code/cli/features/profiler/suite.py +386 -0
- praisonai_code/cli/features/queue/__init__.py +73 -0
- praisonai_code/cli/features/queue/manager.py +435 -0
- praisonai_code/cli/features/queue/models.py +289 -0
- praisonai_code/cli/features/queue/persistence.py +564 -0
- praisonai_code/cli/features/queue/scheduler.py +529 -0
- praisonai_code/cli/features/queue/worker.py +400 -0
- praisonai_code/cli/features/recipe.py +2187 -0
- praisonai_code/cli/features/recipe_creator.py +996 -0
- praisonai_code/cli/features/recipe_optimizer.py +1364 -0
- praisonai_code/cli/features/recipe_prompts.py +226 -0
- praisonai_code/cli/features/registry.py +229 -0
- praisonai_code/cli/features/repo_map.py +860 -0
- praisonai_code/cli/features/router.py +466 -0
- praisonai_code/cli/features/safe_shell.py +427 -0
- praisonai_code/cli/features/sandbox_cli.py +283 -0
- praisonai_code/cli/features/sandbox_executor.py +536 -0
- praisonai_code/cli/features/sdk_knowledge.py +500 -0
- praisonai_code/cli/features/session.py +222 -0
- praisonai_code/cli/features/session_checkpoints.py +208 -0
- praisonai_code/cli/features/setup/__init__.py +9 -0
- praisonai_code/cli/features/setup/handler.py +355 -0
- praisonai_code/cli/features/setup/templates.py +62 -0
- praisonai_code/cli/features/skills.py +940 -0
- praisonai_code/cli/features/slash_commands.py +692 -0
- praisonai_code/cli/features/telemetry.py +179 -0
- praisonai_code/cli/features/templates.py +1390 -0
- praisonai_code/cli/features/thinking.py +343 -0
- praisonai_code/cli/features/todo.py +334 -0
- praisonai_code/cli/features/tools.py +680 -0
- praisonai_code/cli/features/tui/__init__.py +83 -0
- praisonai_code/cli/features/tui/app.py +871 -0
- praisonai_code/cli/features/tui/cli.py +580 -0
- praisonai_code/cli/features/tui/config.py +150 -0
- praisonai_code/cli/features/tui/debug.py +526 -0
- praisonai_code/cli/features/tui/events.py +99 -0
- praisonai_code/cli/features/tui/mock_provider.py +328 -0
- praisonai_code/cli/features/tui/orchestrator.py +652 -0
- praisonai_code/cli/features/tui/screens/__init__.py +50 -0
- praisonai_code/cli/features/tui/screens/help.py +157 -0
- praisonai_code/cli/features/tui/screens/main.py +568 -0
- praisonai_code/cli/features/tui/screens/queue.py +174 -0
- praisonai_code/cli/features/tui/screens/session.py +124 -0
- praisonai_code/cli/features/tui/screens/settings.py +148 -0
- praisonai_code/cli/features/tui/session_store.py +198 -0
- praisonai_code/cli/features/tui/widgets/__init__.py +56 -0
- praisonai_code/cli/features/tui/widgets/chat.py +263 -0
- praisonai_code/cli/features/tui/widgets/command_popup.py +258 -0
- praisonai_code/cli/features/tui/widgets/composer.py +292 -0
- praisonai_code/cli/features/tui/widgets/file_popup.py +207 -0
- praisonai_code/cli/features/tui/widgets/queue_panel.py +223 -0
- praisonai_code/cli/features/tui/widgets/status.py +181 -0
- praisonai_code/cli/features/tui/widgets/tool_panel.py +307 -0
- praisonai_code/cli/features/wizard.py +289 -0
- praisonai_code/cli/features/workflow.py +802 -0
- praisonai_code/cli/features/yaml_utils.py +321 -0
- praisonai_code/cli/interactive/__init__.py +48 -0
- praisonai_code/cli/interactive/async_tui.py +1218 -0
- praisonai_code/cli/interactive/config.py +139 -0
- praisonai_code/cli/interactive/core.py +618 -0
- praisonai_code/cli/interactive/events.py +131 -0
- praisonai_code/cli/interactive/frontends/__init__.py +31 -0
- praisonai_code/cli/interactive/frontends/rich_frontend.py +462 -0
- praisonai_code/cli/interactive/frontends/textual_frontend.py +157 -0
- praisonai_code/cli/interactive/praison_io.py +502 -0
- praisonai_code/cli/interactive/repl.py +297 -0
- praisonai_code/cli/interactive/split_tui.py +456 -0
- praisonai_code/cli/interactive/tui_app.py +457 -0
- praisonai_code/cli/langfuse_client.py +360 -0
- praisonai_code/cli/main.py +7421 -0
- praisonai_code/cli/output/__init__.py +25 -0
- praisonai_code/cli/output/console.py +456 -0
- praisonai_code/cli/output/event_bridge.py +191 -0
- praisonai_code/cli/schedule_cli.py +54 -0
- praisonai_code/cli/schema_provider.py +23 -0
- praisonai_code/cli/session/__init__.py +16 -0
- praisonai_code/cli/session/resume.py +148 -0
- praisonai_code/cli/session/unified.py +548 -0
- praisonai_code/cli/state/__init__.py +31 -0
- praisonai_code/cli/state/identifiers.py +161 -0
- praisonai_code/cli/state/project_sessions.py +383 -0
- praisonai_code/cli/state/sessions.py +390 -0
- praisonai_code/cli/ui/__init__.py +160 -0
- praisonai_code/cli/ui/config.py +46 -0
- praisonai_code/cli/ui/events.py +61 -0
- praisonai_code/cli/ui/mg_backend.py +342 -0
- praisonai_code/cli/ui/plain.py +133 -0
- praisonai_code/cli/ui/rich_backend.py +162 -0
- praisonai_code/cli/unified_schema.py +655 -0
- praisonai_code/cli/utils/env_utils.py +126 -0
- praisonai_code/cli/utils/project.py +131 -0
- praisonai_code/cli_backends/__init__.py +73 -0
- praisonai_code/cli_backends/claude.py +373 -0
- praisonai_code/cli_backends/registry.py +113 -0
- praisonai_code/runtime/__init__.py +36 -0
- praisonai_code/runtime/__main__.py +81 -0
- praisonai_code/runtime/client.py +131 -0
- praisonai_code/runtime/descriptor.py +209 -0
- praisonai_code/runtime/server.py +356 -0
- praisonai_code-0.0.1.dist-info/METADATA +80 -0
- praisonai_code-0.0.1.dist-info/RECORD +309 -0
- praisonai_code-0.0.1.dist-info/WHEEL +5 -0
- praisonai_code-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Event types and dataclasses for InteractiveCore.
|
|
3
|
+
|
|
4
|
+
These events are emitted by InteractiveCore and consumed by frontends
|
|
5
|
+
(Rich REPL, Textual TUI, etc.) for rendering.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Dict, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class InteractiveEventType(Enum):
|
|
16
|
+
"""Types of events emitted by InteractiveCore."""
|
|
17
|
+
|
|
18
|
+
# Message lifecycle
|
|
19
|
+
MESSAGE_START = "message.start"
|
|
20
|
+
MESSAGE_CHUNK = "message.chunk"
|
|
21
|
+
MESSAGE_END = "message.end"
|
|
22
|
+
|
|
23
|
+
# Tool execution
|
|
24
|
+
TOOL_START = "tool.start"
|
|
25
|
+
TOOL_END = "tool.end"
|
|
26
|
+
|
|
27
|
+
# Stage transitions (PLAN → EXPLORE → BUILD → REVIEW)
|
|
28
|
+
STAGE_CHANGE = "stage.change"
|
|
29
|
+
|
|
30
|
+
# Approval/permission flow
|
|
31
|
+
APPROVAL_ASKED = "approval.asked"
|
|
32
|
+
APPROVAL_ANSWERED = "approval.answered"
|
|
33
|
+
|
|
34
|
+
# Session lifecycle
|
|
35
|
+
SESSION_CREATED = "session.created"
|
|
36
|
+
SESSION_RESUMED = "session.resumed"
|
|
37
|
+
|
|
38
|
+
# Status
|
|
39
|
+
ERROR = "error"
|
|
40
|
+
IDLE = "idle"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ApprovalDecision(Enum):
|
|
44
|
+
"""Possible decisions for approval requests."""
|
|
45
|
+
|
|
46
|
+
ONCE = "once" # Allow this one time
|
|
47
|
+
ALWAYS = "always" # Always allow this pattern (persistent)
|
|
48
|
+
ALWAYS_SESSION = "always_session" # Always allow for this session only
|
|
49
|
+
REJECT = "reject" # Reject this action
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class InteractiveEvent:
|
|
54
|
+
"""Event emitted by InteractiveCore."""
|
|
55
|
+
|
|
56
|
+
type: InteractiveEventType
|
|
57
|
+
data: Dict[str, Any] = field(default_factory=dict)
|
|
58
|
+
timestamp: float = field(default_factory=time.time)
|
|
59
|
+
source: Optional[str] = None
|
|
60
|
+
|
|
61
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
62
|
+
"""Convert event to dictionary."""
|
|
63
|
+
return {
|
|
64
|
+
"type": self.type.value,
|
|
65
|
+
"data": self.data,
|
|
66
|
+
"timestamp": self.timestamp,
|
|
67
|
+
"source": self.source,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class ApprovalRequest:
|
|
73
|
+
"""Request for user approval before executing an action."""
|
|
74
|
+
|
|
75
|
+
action_type: str # e.g., "file_write", "shell_command", "file_read"
|
|
76
|
+
description: str # Human-readable description
|
|
77
|
+
tool_name: str # Name of the tool requesting approval
|
|
78
|
+
parameters: Dict[str, Any] # Tool parameters
|
|
79
|
+
request_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
80
|
+
|
|
81
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
82
|
+
"""Convert to dictionary."""
|
|
83
|
+
return {
|
|
84
|
+
"request_id": self.request_id,
|
|
85
|
+
"action_type": self.action_type,
|
|
86
|
+
"description": self.description,
|
|
87
|
+
"tool_name": self.tool_name,
|
|
88
|
+
"parameters": self.parameters,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
def matches_pattern(self, pattern: str) -> bool:
|
|
92
|
+
"""Check if this request matches an approval pattern.
|
|
93
|
+
|
|
94
|
+
Pattern format: "action_type:path_pattern"
|
|
95
|
+
Examples:
|
|
96
|
+
- "file_read:*" matches all file reads
|
|
97
|
+
- "file_write:/tmp/*" matches writes to /tmp/
|
|
98
|
+
- "shell_command:ls*" matches ls commands
|
|
99
|
+
"""
|
|
100
|
+
if ":" not in pattern:
|
|
101
|
+
return self.action_type == pattern
|
|
102
|
+
|
|
103
|
+
action_pattern, path_pattern = pattern.split(":", 1)
|
|
104
|
+
|
|
105
|
+
if action_pattern != "*" and action_pattern != self.action_type:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
if path_pattern == "*":
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
# Simple glob matching
|
|
112
|
+
import fnmatch
|
|
113
|
+
path = self.parameters.get("path", self.parameters.get("command", ""))
|
|
114
|
+
return fnmatch.fnmatch(str(path), path_pattern)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass
|
|
118
|
+
class ApprovalResponse:
|
|
119
|
+
"""Response to an approval request."""
|
|
120
|
+
|
|
121
|
+
request_id: str
|
|
122
|
+
decision: ApprovalDecision
|
|
123
|
+
remember_pattern: Optional[str] = None # Pattern to remember for ALWAYS decisions
|
|
124
|
+
|
|
125
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
126
|
+
"""Convert to dictionary."""
|
|
127
|
+
return {
|
|
128
|
+
"request_id": self.request_id,
|
|
129
|
+
"decision": self.decision.value,
|
|
130
|
+
"remember_pattern": self.remember_pattern,
|
|
131
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Frontend implementations for InteractiveCore.
|
|
3
|
+
|
|
4
|
+
Each frontend subscribes to InteractiveCore events and renders them
|
|
5
|
+
using its specific UI framework.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"RichFrontend",
|
|
10
|
+
"TextualFrontend",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
_lazy_cache = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def __getattr__(name: str):
|
|
17
|
+
"""Lazy load frontends."""
|
|
18
|
+
if name in _lazy_cache:
|
|
19
|
+
return _lazy_cache[name]
|
|
20
|
+
|
|
21
|
+
if name == "RichFrontend":
|
|
22
|
+
from .rich_frontend import RichFrontend
|
|
23
|
+
_lazy_cache[name] = RichFrontend
|
|
24
|
+
return RichFrontend
|
|
25
|
+
|
|
26
|
+
if name == "TextualFrontend":
|
|
27
|
+
from .textual_frontend import TextualFrontend
|
|
28
|
+
_lazy_cache[name] = TextualFrontend
|
|
29
|
+
return TextualFrontend
|
|
30
|
+
|
|
31
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rich/prompt_toolkit frontend for InteractiveCore.
|
|
3
|
+
|
|
4
|
+
This frontend provides a REPL-style interface using Rich for rendering
|
|
5
|
+
and prompt_toolkit for input handling.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from ..core import InteractiveCore
|
|
13
|
+
from ..config import InteractiveConfig
|
|
14
|
+
from ..events import (
|
|
15
|
+
InteractiveEvent,
|
|
16
|
+
InteractiveEventType,
|
|
17
|
+
ApprovalRequest,
|
|
18
|
+
ApprovalResponse,
|
|
19
|
+
ApprovalDecision,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RichFrontend:
|
|
26
|
+
"""
|
|
27
|
+
Rich/prompt_toolkit frontend for interactive mode.
|
|
28
|
+
|
|
29
|
+
This is used by:
|
|
30
|
+
- `praisonai run --interactive`
|
|
31
|
+
- `praisonai chat`
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
core: Optional[InteractiveCore] = None,
|
|
37
|
+
config: Optional[InteractiveConfig] = None,
|
|
38
|
+
ui_config: Optional['UIConfig'] = None,
|
|
39
|
+
):
|
|
40
|
+
"""Initialize the Rich frontend.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
core: InteractiveCore instance. If None, creates one.
|
|
44
|
+
config: Configuration. Used if core is None.
|
|
45
|
+
ui_config: UI backend configuration for rendering options.
|
|
46
|
+
"""
|
|
47
|
+
self.core = core or InteractiveCore(config=config)
|
|
48
|
+
self._ui_config = ui_config
|
|
49
|
+
self._running = False
|
|
50
|
+
self._current_response = ""
|
|
51
|
+
self._pending_approval: Optional[ApprovalRequest] = None
|
|
52
|
+
self._approval_event = asyncio.Event()
|
|
53
|
+
self._approval_response: Optional[ApprovalResponse] = None
|
|
54
|
+
|
|
55
|
+
# Subscribe to events
|
|
56
|
+
self.core.subscribe(self._handle_event)
|
|
57
|
+
|
|
58
|
+
def _handle_event(self, event: InteractiveEvent) -> None:
|
|
59
|
+
"""Handle events from InteractiveCore."""
|
|
60
|
+
if event.type == InteractiveEventType.MESSAGE_START:
|
|
61
|
+
self._on_message_start(event)
|
|
62
|
+
elif event.type == InteractiveEventType.MESSAGE_CHUNK:
|
|
63
|
+
self._on_message_chunk(event)
|
|
64
|
+
elif event.type == InteractiveEventType.MESSAGE_END:
|
|
65
|
+
self._on_message_end(event)
|
|
66
|
+
elif event.type == InteractiveEventType.TOOL_START:
|
|
67
|
+
self._on_tool_start(event)
|
|
68
|
+
elif event.type == InteractiveEventType.TOOL_END:
|
|
69
|
+
self._on_tool_end(event)
|
|
70
|
+
elif event.type == InteractiveEventType.APPROVAL_ASKED:
|
|
71
|
+
self._on_approval_asked(event)
|
|
72
|
+
elif event.type == InteractiveEventType.ERROR:
|
|
73
|
+
self._on_error(event)
|
|
74
|
+
elif event.type == InteractiveEventType.SESSION_CREATED:
|
|
75
|
+
self._on_session_created(event)
|
|
76
|
+
elif event.type == InteractiveEventType.SESSION_RESUMED:
|
|
77
|
+
self._on_session_resumed(event)
|
|
78
|
+
|
|
79
|
+
def _on_message_start(self, event: InteractiveEvent) -> None:
|
|
80
|
+
"""Handle message start."""
|
|
81
|
+
try:
|
|
82
|
+
from rich.console import Console
|
|
83
|
+
console = Console()
|
|
84
|
+
console.print("\n[bold cyan]Assistant:[/bold cyan]", end=" ")
|
|
85
|
+
except ImportError:
|
|
86
|
+
print("\nAssistant:", end=" ")
|
|
87
|
+
|
|
88
|
+
def _on_message_chunk(self, event: InteractiveEvent) -> None:
|
|
89
|
+
"""Handle message chunk (streaming)."""
|
|
90
|
+
chunk = event.data.get("chunk", "")
|
|
91
|
+
self._current_response += chunk
|
|
92
|
+
print(chunk, end="", flush=True)
|
|
93
|
+
|
|
94
|
+
def _on_message_end(self, event: InteractiveEvent) -> None:
|
|
95
|
+
"""Handle message end."""
|
|
96
|
+
response = event.data.get("response", "")
|
|
97
|
+
if response and not self._current_response:
|
|
98
|
+
# Full response (non-streaming)
|
|
99
|
+
print(response)
|
|
100
|
+
else:
|
|
101
|
+
print() # Newline after streaming
|
|
102
|
+
self._current_response = ""
|
|
103
|
+
|
|
104
|
+
def _on_tool_start(self, event: InteractiveEvent) -> None:
|
|
105
|
+
"""Handle tool execution start."""
|
|
106
|
+
tool_name = event.data.get("tool_name", "unknown")
|
|
107
|
+
try:
|
|
108
|
+
from rich.console import Console
|
|
109
|
+
console = Console()
|
|
110
|
+
console.print(f"[dim]⚙ Running {tool_name}...[/dim]")
|
|
111
|
+
except ImportError:
|
|
112
|
+
print(f"⚙ Running {tool_name}...")
|
|
113
|
+
|
|
114
|
+
def _on_tool_end(self, event: InteractiveEvent) -> None:
|
|
115
|
+
"""Handle tool execution end."""
|
|
116
|
+
tool_name = event.data.get("tool_name", "unknown")
|
|
117
|
+
success = event.data.get("success", True)
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
from rich.console import Console
|
|
121
|
+
console = Console()
|
|
122
|
+
if success:
|
|
123
|
+
console.print(f"[dim]✓ {tool_name} completed[/dim]")
|
|
124
|
+
else:
|
|
125
|
+
console.print(f"[red]✗ {tool_name} failed[/red]")
|
|
126
|
+
except ImportError:
|
|
127
|
+
status = "✓" if success else "✗"
|
|
128
|
+
print(f"{status} {tool_name} {'completed' if success else 'failed'}")
|
|
129
|
+
|
|
130
|
+
def _on_approval_asked(self, event: InteractiveEvent) -> None:
|
|
131
|
+
"""Handle approval request."""
|
|
132
|
+
self._pending_approval = ApprovalRequest(**event.data)
|
|
133
|
+
|
|
134
|
+
def _on_error(self, event: InteractiveEvent) -> None:
|
|
135
|
+
"""Handle error."""
|
|
136
|
+
error = event.data.get("error", "Unknown error")
|
|
137
|
+
try:
|
|
138
|
+
from rich.console import Console
|
|
139
|
+
console = Console()
|
|
140
|
+
console.print(f"[bold red]Error:[/bold red] {error}")
|
|
141
|
+
except ImportError:
|
|
142
|
+
print(f"Error: {error}")
|
|
143
|
+
|
|
144
|
+
def _on_session_created(self, event: InteractiveEvent) -> None:
|
|
145
|
+
"""Handle session created."""
|
|
146
|
+
session_id = event.data.get("session_id", "")
|
|
147
|
+
try:
|
|
148
|
+
from rich.console import Console
|
|
149
|
+
console = Console()
|
|
150
|
+
console.print(f"[dim]Session created: {session_id[:8]}...[/dim]")
|
|
151
|
+
except ImportError:
|
|
152
|
+
print(f"Session created: {session_id[:8]}...")
|
|
153
|
+
|
|
154
|
+
def _on_session_resumed(self, event: InteractiveEvent) -> None:
|
|
155
|
+
"""Handle session resumed."""
|
|
156
|
+
session_id = event.data.get("session_id", "")
|
|
157
|
+
try:
|
|
158
|
+
from rich.console import Console
|
|
159
|
+
console = Console()
|
|
160
|
+
console.print(f"[dim]Session resumed: {session_id[:8]}...[/dim]")
|
|
161
|
+
except ImportError:
|
|
162
|
+
print(f"Session resumed: {session_id[:8]}...")
|
|
163
|
+
|
|
164
|
+
def _prompt_approval(self, request: ApprovalRequest) -> ApprovalResponse:
|
|
165
|
+
"""Prompt user for approval decision."""
|
|
166
|
+
try:
|
|
167
|
+
from rich.console import Console
|
|
168
|
+
from rich.panel import Panel
|
|
169
|
+
console = Console()
|
|
170
|
+
|
|
171
|
+
console.print(Panel(
|
|
172
|
+
f"[bold]{request.description}[/bold]\n\n"
|
|
173
|
+
f"Tool: {request.tool_name}\n"
|
|
174
|
+
f"Action: {request.action_type}",
|
|
175
|
+
title="[yellow]Approval Required[/yellow]",
|
|
176
|
+
border_style="yellow"
|
|
177
|
+
))
|
|
178
|
+
|
|
179
|
+
console.print("[1] Allow once")
|
|
180
|
+
console.print("[2] Always allow this pattern")
|
|
181
|
+
console.print("[3] Always allow for this session")
|
|
182
|
+
console.print("[4] Reject")
|
|
183
|
+
|
|
184
|
+
except ImportError:
|
|
185
|
+
print(f"\n=== Approval Required ===")
|
|
186
|
+
print(f"Description: {request.description}")
|
|
187
|
+
print(f"Tool: {request.tool_name}")
|
|
188
|
+
print(f"Action: {request.action_type}")
|
|
189
|
+
print("[1] Allow once")
|
|
190
|
+
print("[2] Always allow this pattern")
|
|
191
|
+
print("[3] Always allow for this session")
|
|
192
|
+
print("[4] Reject")
|
|
193
|
+
|
|
194
|
+
while True:
|
|
195
|
+
try:
|
|
196
|
+
choice = input("\nChoice [1-4]: ").strip()
|
|
197
|
+
|
|
198
|
+
if choice == "1":
|
|
199
|
+
return ApprovalResponse(
|
|
200
|
+
request_id=request.request_id,
|
|
201
|
+
decision=ApprovalDecision.ONCE
|
|
202
|
+
)
|
|
203
|
+
elif choice == "2":
|
|
204
|
+
pattern = f"{request.action_type}:*"
|
|
205
|
+
return ApprovalResponse(
|
|
206
|
+
request_id=request.request_id,
|
|
207
|
+
decision=ApprovalDecision.ALWAYS,
|
|
208
|
+
remember_pattern=pattern
|
|
209
|
+
)
|
|
210
|
+
elif choice == "3":
|
|
211
|
+
pattern = f"{request.action_type}:*"
|
|
212
|
+
return ApprovalResponse(
|
|
213
|
+
request_id=request.request_id,
|
|
214
|
+
decision=ApprovalDecision.ALWAYS_SESSION,
|
|
215
|
+
remember_pattern=pattern
|
|
216
|
+
)
|
|
217
|
+
elif choice == "4":
|
|
218
|
+
return ApprovalResponse(
|
|
219
|
+
request_id=request.request_id,
|
|
220
|
+
decision=ApprovalDecision.REJECT
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
print("Invalid choice. Please enter 1-4.")
|
|
224
|
+
except (EOFError, KeyboardInterrupt):
|
|
225
|
+
return ApprovalResponse(
|
|
226
|
+
request_id=request.request_id,
|
|
227
|
+
decision=ApprovalDecision.REJECT
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
async def run(self) -> None:
|
|
231
|
+
"""Run the interactive REPL loop."""
|
|
232
|
+
self._running = True
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
from rich.console import Console
|
|
236
|
+
console = Console()
|
|
237
|
+
console.print("[bold green]PraisonAI Interactive Mode[/bold green]")
|
|
238
|
+
console.print("[dim]Type /help for commands, /exit to quit[/dim]\n")
|
|
239
|
+
except ImportError:
|
|
240
|
+
print("PraisonAI Interactive Mode")
|
|
241
|
+
print("Type /help for commands, /exit to quit\n")
|
|
242
|
+
|
|
243
|
+
# Handle --continue flag
|
|
244
|
+
if self.core.config.continue_session:
|
|
245
|
+
session_id = self.core.continue_session()
|
|
246
|
+
if not session_id:
|
|
247
|
+
try:
|
|
248
|
+
from rich.console import Console
|
|
249
|
+
Console().print("[yellow]No previous session found. Starting new session.[/yellow]")
|
|
250
|
+
except ImportError:
|
|
251
|
+
print("No previous session found. Starting new session.")
|
|
252
|
+
|
|
253
|
+
# Try to use prompt_toolkit for better input
|
|
254
|
+
try:
|
|
255
|
+
from prompt_toolkit import PromptSession
|
|
256
|
+
from prompt_toolkit.history import FileHistory
|
|
257
|
+
from pathlib import Path
|
|
258
|
+
|
|
259
|
+
history_file = Path.home() / ".praison" / "history"
|
|
260
|
+
history_file.parent.mkdir(parents=True, exist_ok=True)
|
|
261
|
+
|
|
262
|
+
session = PromptSession(history=FileHistory(str(history_file)))
|
|
263
|
+
get_input = lambda: session.prompt("You: ")
|
|
264
|
+
except ImportError:
|
|
265
|
+
get_input = lambda: input("You: ")
|
|
266
|
+
|
|
267
|
+
while self._running:
|
|
268
|
+
try:
|
|
269
|
+
user_input = get_input().strip()
|
|
270
|
+
|
|
271
|
+
if not user_input:
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
# Handle slash commands
|
|
275
|
+
if user_input.startswith("/"):
|
|
276
|
+
if await self._handle_command(user_input):
|
|
277
|
+
continue
|
|
278
|
+
|
|
279
|
+
# Execute prompt
|
|
280
|
+
await self.core.prompt(user_input)
|
|
281
|
+
|
|
282
|
+
except (EOFError, KeyboardInterrupt):
|
|
283
|
+
print("\nGoodbye!")
|
|
284
|
+
break
|
|
285
|
+
except Exception as e:
|
|
286
|
+
logger.exception("Error in REPL loop")
|
|
287
|
+
try:
|
|
288
|
+
from rich.console import Console
|
|
289
|
+
Console().print(f"[red]Error: {e}[/red]")
|
|
290
|
+
except ImportError:
|
|
291
|
+
print(f"Error: {e}")
|
|
292
|
+
|
|
293
|
+
async def _handle_command(self, command: str) -> bool:
|
|
294
|
+
"""Handle slash commands. Returns True if command was handled."""
|
|
295
|
+
cmd = command.lower().split()[0]
|
|
296
|
+
args = command.split()[1:] if len(command.split()) > 1 else []
|
|
297
|
+
|
|
298
|
+
try:
|
|
299
|
+
from rich.console import Console
|
|
300
|
+
console = Console()
|
|
301
|
+
except ImportError:
|
|
302
|
+
console = None
|
|
303
|
+
|
|
304
|
+
if cmd in ("/exit", "/quit", "/q"):
|
|
305
|
+
self._running = False
|
|
306
|
+
return True
|
|
307
|
+
|
|
308
|
+
elif cmd == "/help":
|
|
309
|
+
help_text = """
|
|
310
|
+
Available commands:
|
|
311
|
+
/help - Show this help
|
|
312
|
+
/exit, /quit - Exit interactive mode
|
|
313
|
+
/clear - Clear conversation history
|
|
314
|
+
/session - Show current session info
|
|
315
|
+
/sessions - List all sessions
|
|
316
|
+
/continue - Continue last session
|
|
317
|
+
/export <file> - Export session to file
|
|
318
|
+
/import <file> - Import session from file
|
|
319
|
+
/model <name> - Switch model
|
|
320
|
+
/cost - Show token/cost usage
|
|
321
|
+
"""
|
|
322
|
+
if console:
|
|
323
|
+
console.print(help_text)
|
|
324
|
+
else:
|
|
325
|
+
print(help_text)
|
|
326
|
+
return True
|
|
327
|
+
|
|
328
|
+
elif cmd == "/clear":
|
|
329
|
+
# Create new session
|
|
330
|
+
self.core.create_session()
|
|
331
|
+
if console:
|
|
332
|
+
console.print("[dim]Conversation cleared.[/dim]")
|
|
333
|
+
else:
|
|
334
|
+
print("Conversation cleared.")
|
|
335
|
+
return True
|
|
336
|
+
|
|
337
|
+
elif cmd == "/session":
|
|
338
|
+
session_id = self.core.current_session_id
|
|
339
|
+
if session_id:
|
|
340
|
+
if console:
|
|
341
|
+
console.print(f"[dim]Current session: {session_id}[/dim]")
|
|
342
|
+
else:
|
|
343
|
+
print(f"Current session: {session_id}")
|
|
344
|
+
else:
|
|
345
|
+
if console:
|
|
346
|
+
console.print("[dim]No active session[/dim]")
|
|
347
|
+
else:
|
|
348
|
+
print("No active session")
|
|
349
|
+
return True
|
|
350
|
+
|
|
351
|
+
elif cmd == "/sessions":
|
|
352
|
+
sessions = self.core.session_store.list_sessions()
|
|
353
|
+
if sessions:
|
|
354
|
+
if console:
|
|
355
|
+
console.print("[bold]Sessions:[/bold]")
|
|
356
|
+
for s in sessions[:10]:
|
|
357
|
+
sid = s.get("session_id", "")[:8]
|
|
358
|
+
title = s.get("title", "Untitled")
|
|
359
|
+
console.print(f" {sid}... - {title}")
|
|
360
|
+
else:
|
|
361
|
+
print("Sessions:")
|
|
362
|
+
for s in sessions[:10]:
|
|
363
|
+
print(f" {s.get('session_id', '')[:8]}... - {s.get('title', 'Untitled')}")
|
|
364
|
+
else:
|
|
365
|
+
if console:
|
|
366
|
+
console.print("[dim]No sessions found[/dim]")
|
|
367
|
+
else:
|
|
368
|
+
print("No sessions found")
|
|
369
|
+
return True
|
|
370
|
+
|
|
371
|
+
elif cmd == "/continue":
|
|
372
|
+
session_id = self.core.continue_session()
|
|
373
|
+
if session_id:
|
|
374
|
+
if console:
|
|
375
|
+
console.print(f"[dim]Continued session: {session_id[:8]}...[/dim]")
|
|
376
|
+
else:
|
|
377
|
+
print(f"Continued session: {session_id[:8]}...")
|
|
378
|
+
else:
|
|
379
|
+
if console:
|
|
380
|
+
console.print("[yellow]No previous session found[/yellow]")
|
|
381
|
+
else:
|
|
382
|
+
print("No previous session found")
|
|
383
|
+
return True
|
|
384
|
+
|
|
385
|
+
elif cmd == "/export":
|
|
386
|
+
if not args:
|
|
387
|
+
if console:
|
|
388
|
+
console.print("[red]Usage: /export <filename>[/red]")
|
|
389
|
+
else:
|
|
390
|
+
print("Usage: /export <filename>")
|
|
391
|
+
return True
|
|
392
|
+
|
|
393
|
+
session_id = self.core.current_session_id
|
|
394
|
+
if session_id:
|
|
395
|
+
try:
|
|
396
|
+
self.core.export_session_to_file(session_id, args[0])
|
|
397
|
+
if console:
|
|
398
|
+
console.print(f"[green]Session exported to {args[0]}[/green]")
|
|
399
|
+
else:
|
|
400
|
+
print(f"Session exported to {args[0]}")
|
|
401
|
+
except Exception as e:
|
|
402
|
+
if console:
|
|
403
|
+
console.print(f"[red]Export failed: {e}[/red]")
|
|
404
|
+
else:
|
|
405
|
+
print(f"Export failed: {e}")
|
|
406
|
+
else:
|
|
407
|
+
if console:
|
|
408
|
+
console.print("[yellow]No active session to export[/yellow]")
|
|
409
|
+
else:
|
|
410
|
+
print("No active session to export")
|
|
411
|
+
return True
|
|
412
|
+
|
|
413
|
+
elif cmd == "/import":
|
|
414
|
+
if not args:
|
|
415
|
+
if console:
|
|
416
|
+
console.print("[red]Usage: /import <filename>[/red]")
|
|
417
|
+
else:
|
|
418
|
+
print("Usage: /import <filename>")
|
|
419
|
+
return True
|
|
420
|
+
|
|
421
|
+
try:
|
|
422
|
+
session_id = self.core.import_session_from_file(args[0])
|
|
423
|
+
self.core.resume_session(session_id)
|
|
424
|
+
if console:
|
|
425
|
+
console.print(f"[green]Session imported: {session_id[:8]}...[/green]")
|
|
426
|
+
else:
|
|
427
|
+
print(f"Session imported: {session_id[:8]}...")
|
|
428
|
+
except Exception as e:
|
|
429
|
+
if console:
|
|
430
|
+
console.print(f"[red]Import failed: {e}[/red]")
|
|
431
|
+
else:
|
|
432
|
+
print(f"Import failed: {e}")
|
|
433
|
+
return True
|
|
434
|
+
|
|
435
|
+
elif cmd == "/model":
|
|
436
|
+
if args:
|
|
437
|
+
self.core.config.model = args[0]
|
|
438
|
+
if console:
|
|
439
|
+
console.print(f"[dim]Model set to: {args[0]}[/dim]")
|
|
440
|
+
else:
|
|
441
|
+
print(f"Model set to: {args[0]}")
|
|
442
|
+
else:
|
|
443
|
+
model = self.core.config.model or "default"
|
|
444
|
+
if console:
|
|
445
|
+
console.print(f"[dim]Current model: {model}[/dim]")
|
|
446
|
+
else:
|
|
447
|
+
print(f"Current model: {model}")
|
|
448
|
+
return True
|
|
449
|
+
|
|
450
|
+
elif cmd == "/cost":
|
|
451
|
+
# TODO: Integrate with cost tracker
|
|
452
|
+
if console:
|
|
453
|
+
console.print("[dim]Cost tracking not yet implemented in unified core[/dim]")
|
|
454
|
+
else:
|
|
455
|
+
print("Cost tracking not yet implemented in unified core")
|
|
456
|
+
return True
|
|
457
|
+
|
|
458
|
+
return False # Command not recognized
|
|
459
|
+
|
|
460
|
+
def stop(self) -> None:
|
|
461
|
+
"""Stop the REPL loop."""
|
|
462
|
+
self._running = False
|