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,450 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Session command group for PraisonAI CLI.
|
|
3
|
+
|
|
4
|
+
Provides session management:
|
|
5
|
+
- session list: List all sessions
|
|
6
|
+
- session resume: Resume a session
|
|
7
|
+
- session delete: Delete a session
|
|
8
|
+
- session export: Export a session
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
|
|
15
|
+
from ..output.console import get_output_controller
|
|
16
|
+
from ..state.sessions import get_session_manager, set_session_backend
|
|
17
|
+
|
|
18
|
+
app = typer.Typer(help="Session management")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _create_backend(backend_type: str, storage_path: Optional[str]):
|
|
22
|
+
"""Create storage backend from CLI options."""
|
|
23
|
+
try:
|
|
24
|
+
if backend_type == "file":
|
|
25
|
+
from praisonaiagents.storage import FileBackend
|
|
26
|
+
return FileBackend(storage_dir=storage_path or "~/.praison/sessions")
|
|
27
|
+
elif backend_type == "sqlite":
|
|
28
|
+
from praisonaiagents.storage import SQLiteBackend
|
|
29
|
+
db_path = storage_path or "~/.praison/sessions.db"
|
|
30
|
+
return SQLiteBackend(db_path=db_path)
|
|
31
|
+
elif backend_type.startswith("redis://"):
|
|
32
|
+
from praisonaiagents.storage import RedisBackend
|
|
33
|
+
return RedisBackend(url=backend_type, prefix="session:")
|
|
34
|
+
else:
|
|
35
|
+
return None
|
|
36
|
+
except Exception:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@app.command("list")
|
|
41
|
+
def session_list(
|
|
42
|
+
limit: int = typer.Option(
|
|
43
|
+
20,
|
|
44
|
+
"--limit",
|
|
45
|
+
"-n",
|
|
46
|
+
help="Maximum number of sessions to show",
|
|
47
|
+
),
|
|
48
|
+
storage_backend: Optional[str] = typer.Option(
|
|
49
|
+
None,
|
|
50
|
+
"--storage-backend",
|
|
51
|
+
help="Storage backend: 'file', 'sqlite', or 'redis://url'",
|
|
52
|
+
),
|
|
53
|
+
storage_path: Optional[str] = typer.Option(
|
|
54
|
+
None,
|
|
55
|
+
"--storage-path",
|
|
56
|
+
help="Path for storage backend",
|
|
57
|
+
),
|
|
58
|
+
all_projects: bool = typer.Option(
|
|
59
|
+
False,
|
|
60
|
+
"--all",
|
|
61
|
+
help="Show sessions from all projects (default: current project only)",
|
|
62
|
+
),
|
|
63
|
+
project_id: Optional[str] = typer.Option(
|
|
64
|
+
None,
|
|
65
|
+
"--project",
|
|
66
|
+
help="Show sessions for specific project ID",
|
|
67
|
+
),
|
|
68
|
+
):
|
|
69
|
+
"""List all sessions."""
|
|
70
|
+
output = get_output_controller()
|
|
71
|
+
|
|
72
|
+
# Handle project-scoped session listing
|
|
73
|
+
if not all_projects and not storage_backend:
|
|
74
|
+
# Use project-scoped session store by default
|
|
75
|
+
from ..state.project_sessions import get_project_session_store
|
|
76
|
+
from ..utils.project import get_project_id, get_project_name
|
|
77
|
+
|
|
78
|
+
# List sessions for specific or current project
|
|
79
|
+
project_store = get_project_session_store(project_id=project_id)
|
|
80
|
+
|
|
81
|
+
sessions_data = project_store.list_sessions(limit=limit)
|
|
82
|
+
|
|
83
|
+
# Convert to expected format
|
|
84
|
+
class SessionInfo:
|
|
85
|
+
def __init__(self, data):
|
|
86
|
+
from datetime import datetime
|
|
87
|
+
|
|
88
|
+
self.session_id = data.get("session_id", data.get("id", ""))
|
|
89
|
+
self.name = data.get("agent_name", "")
|
|
90
|
+
self.status = data.get("status") # Use actual status from data if available
|
|
91
|
+
self.event_count = data.get("message_count", 0)
|
|
92
|
+
|
|
93
|
+
# Cumulative usage totals persisted per session (Issue #2421).
|
|
94
|
+
usage = data.get("usage")
|
|
95
|
+
if isinstance(usage, dict):
|
|
96
|
+
self.total_tokens = usage.get("total_tokens") or 0
|
|
97
|
+
self.cost = usage.get("cost") or 0.0
|
|
98
|
+
else:
|
|
99
|
+
self.total_tokens = data.get("total_tokens") or 0
|
|
100
|
+
self.cost = data.get("cost") or 0.0
|
|
101
|
+
|
|
102
|
+
# Parse updated_at string
|
|
103
|
+
updated_str = data.get("updated_at", "")
|
|
104
|
+
try:
|
|
105
|
+
self.updated_at = datetime.fromisoformat(updated_str.replace('Z', '+00:00')) if updated_str else datetime.now()
|
|
106
|
+
except ValueError:
|
|
107
|
+
self.updated_at = datetime.now()
|
|
108
|
+
|
|
109
|
+
def to_dict(self):
|
|
110
|
+
return {
|
|
111
|
+
"session_id": self.session_id,
|
|
112
|
+
"name": self.name,
|
|
113
|
+
"status": self.status,
|
|
114
|
+
"event_count": self.event_count,
|
|
115
|
+
"total_tokens": self.total_tokens,
|
|
116
|
+
"cost": self.cost,
|
|
117
|
+
"updated_at": self.updated_at.isoformat(),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
sessions = [SessionInfo(data) for data in sessions_data]
|
|
121
|
+
|
|
122
|
+
# Add project info to output
|
|
123
|
+
if not project_id:
|
|
124
|
+
current_project = get_project_name()
|
|
125
|
+
current_id = get_project_id()
|
|
126
|
+
output.print_info(f"Project: {current_project} (ID: {current_id})")
|
|
127
|
+
|
|
128
|
+
else:
|
|
129
|
+
# Use global session manager
|
|
130
|
+
# Configure backend if specified
|
|
131
|
+
if storage_backend:
|
|
132
|
+
backend = _create_backend(storage_backend, storage_path)
|
|
133
|
+
if backend:
|
|
134
|
+
set_session_backend(backend)
|
|
135
|
+
|
|
136
|
+
manager = get_session_manager()
|
|
137
|
+
sessions = manager.list(limit=limit)
|
|
138
|
+
|
|
139
|
+
# SessionMetadata objects don't carry usage; surface persisted totals
|
|
140
|
+
# for global/backend listings the same way the project path does so the
|
|
141
|
+
# Tokens/Cost columns aren't blank when usage was recorded (Issue #2421).
|
|
142
|
+
try:
|
|
143
|
+
from ..state.project_sessions import read_session_usage
|
|
144
|
+
|
|
145
|
+
for s in sessions:
|
|
146
|
+
sid = getattr(s, "session_id", None) or getattr(s, "id", None)
|
|
147
|
+
if not sid:
|
|
148
|
+
continue
|
|
149
|
+
usage = read_session_usage(sid)
|
|
150
|
+
if usage.get("total_tokens") or usage.get("cost"):
|
|
151
|
+
try:
|
|
152
|
+
s.total_tokens = usage.get("total_tokens") or 0
|
|
153
|
+
s.cost = usage.get("cost") or 0.0
|
|
154
|
+
except Exception:
|
|
155
|
+
pass
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
if output.is_json_mode:
|
|
160
|
+
def _session_dict(s):
|
|
161
|
+
d = s.to_dict()
|
|
162
|
+
# Ensure usage totals surface in JSON even for SessionMetadata,
|
|
163
|
+
# whose to_dict() omits them (Issue #2421).
|
|
164
|
+
if "total_tokens" not in d:
|
|
165
|
+
d["total_tokens"] = getattr(s, "total_tokens", 0) or 0
|
|
166
|
+
if "cost" not in d:
|
|
167
|
+
d["cost"] = getattr(s, "cost", 0.0) or 0.0
|
|
168
|
+
return d
|
|
169
|
+
|
|
170
|
+
output.print_json({
|
|
171
|
+
"sessions": [_session_dict(s) for s in sessions]
|
|
172
|
+
})
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
if not sessions:
|
|
176
|
+
output.print_info("No sessions found")
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
headers = ["ID", "Name", "Status", "Events", "Tokens", "Cost", "Updated"]
|
|
180
|
+
rows = []
|
|
181
|
+
for s in sessions:
|
|
182
|
+
total_tokens = getattr(s, "total_tokens", 0) or 0
|
|
183
|
+
cost = getattr(s, "cost", 0.0) or 0.0
|
|
184
|
+
rows.append([
|
|
185
|
+
s.session_id[:20] + "..." if len(s.session_id) > 20 else s.session_id,
|
|
186
|
+
s.name or "-",
|
|
187
|
+
s.status,
|
|
188
|
+
str(s.event_count),
|
|
189
|
+
f"{int(total_tokens):,}" if total_tokens else "-",
|
|
190
|
+
f"${float(cost):.4f}" if cost else "-",
|
|
191
|
+
s.updated_at.strftime("%Y-%m-%d %H:%M"),
|
|
192
|
+
])
|
|
193
|
+
|
|
194
|
+
output.print_table(headers, rows, title="Sessions")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@app.command("resume")
|
|
198
|
+
def session_resume(
|
|
199
|
+
session_id: str = typer.Argument(..., help="Session ID to resume"),
|
|
200
|
+
prompt: Optional[str] = typer.Argument(
|
|
201
|
+
None,
|
|
202
|
+
help="Optional prompt to continue the session with",
|
|
203
|
+
),
|
|
204
|
+
transcript: bool = typer.Option(
|
|
205
|
+
False,
|
|
206
|
+
"--transcript",
|
|
207
|
+
help="Only show the session transcript instead of restoring state",
|
|
208
|
+
),
|
|
209
|
+
):
|
|
210
|
+
"""Resume a session with full conversational state restored."""
|
|
211
|
+
output = get_output_controller()
|
|
212
|
+
|
|
213
|
+
# Transcript-only path (opt-in for the old behaviour).
|
|
214
|
+
if transcript:
|
|
215
|
+
_print_session_transcript(session_id, output)
|
|
216
|
+
return
|
|
217
|
+
|
|
218
|
+
# Deterministic restoration via the shared rehydrate helper.
|
|
219
|
+
from ..session.resume import rehydrate_session
|
|
220
|
+
|
|
221
|
+
restored = rehydrate_session(session_id)
|
|
222
|
+
|
|
223
|
+
if not restored.found:
|
|
224
|
+
output.print_error(
|
|
225
|
+
f"Session not found: {session_id}",
|
|
226
|
+
remediation="Use 'praisonai session list' to see available sessions"
|
|
227
|
+
)
|
|
228
|
+
raise typer.Exit(1)
|
|
229
|
+
|
|
230
|
+
# When a prompt is supplied we hand off to `_run_prompt`, which owns all
|
|
231
|
+
# output for the continuation run. Emitting a restore blob here too would
|
|
232
|
+
# produce two top-level outputs (and break `--json` consumers), so we skip
|
|
233
|
+
# the standalone restore rendering in that case.
|
|
234
|
+
if prompt is None:
|
|
235
|
+
if output.is_json_mode:
|
|
236
|
+
output.print_json({
|
|
237
|
+
"session": restored.to_dict(),
|
|
238
|
+
"restored": True,
|
|
239
|
+
})
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
from ..state.project_sessions import format_usage_footer
|
|
243
|
+
|
|
244
|
+
usage_line = ""
|
|
245
|
+
if restored.usage and (restored.usage.get("total_tokens") or restored.usage.get("cost")):
|
|
246
|
+
usage_line = f"\nUsage: {format_usage_footer(restored.usage)}"
|
|
247
|
+
|
|
248
|
+
output.print_panel(
|
|
249
|
+
f"Session: {restored.agent_name or restored.session_id}\n"
|
|
250
|
+
f"Model: {restored.model or 'default'}\n"
|
|
251
|
+
f"Messages restored: {len(restored.chat_history)}"
|
|
252
|
+
f"{usage_line}",
|
|
253
|
+
title="Session Resumed"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if restored.chat_history:
|
|
257
|
+
output.print("\n--- Restored Conversation ---\n")
|
|
258
|
+
for msg in restored.chat_history[-10:]:
|
|
259
|
+
role = msg.get("role", "?")
|
|
260
|
+
content = msg.get("content", "")
|
|
261
|
+
output.print(f"[{role}] {content}")
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
# Continue the run with the restored state via the shared run path so
|
|
265
|
+
# behaviour matches `praisonai run --session <id>`.
|
|
266
|
+
from .run import _run_prompt
|
|
267
|
+
|
|
268
|
+
_run_prompt(
|
|
269
|
+
prompt=prompt,
|
|
270
|
+
model=restored.model,
|
|
271
|
+
session=session_id,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _print_session_transcript(session_id: str, output) -> None:
|
|
276
|
+
"""Print a session transcript (legacy ``--transcript`` behaviour)."""
|
|
277
|
+
manager = get_session_manager()
|
|
278
|
+
session = manager.get(session_id)
|
|
279
|
+
|
|
280
|
+
if not session:
|
|
281
|
+
output.print_error(
|
|
282
|
+
f"Session not found: {session_id}",
|
|
283
|
+
remediation="Use 'praisonai session list' to see available sessions"
|
|
284
|
+
)
|
|
285
|
+
raise typer.Exit(1)
|
|
286
|
+
|
|
287
|
+
events = manager.get_events(session_id)
|
|
288
|
+
|
|
289
|
+
if output.is_json_mode:
|
|
290
|
+
output.print_json({
|
|
291
|
+
"session": session.to_dict(),
|
|
292
|
+
"events": events,
|
|
293
|
+
})
|
|
294
|
+
return
|
|
295
|
+
|
|
296
|
+
output.print_panel(
|
|
297
|
+
f"Session: {session.name or session.session_id}\n"
|
|
298
|
+
f"Run ID: {session.run_id}\n"
|
|
299
|
+
f"Trace ID: {session.trace_id}\n"
|
|
300
|
+
f"Events: {session.event_count}\n"
|
|
301
|
+
f"Status: {session.status}",
|
|
302
|
+
title="Session Transcript"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if events:
|
|
306
|
+
output.print("\n--- Recent Events ---\n")
|
|
307
|
+
for event in events[-10:]:
|
|
308
|
+
event_type = event.get("event", "unknown")
|
|
309
|
+
message = event.get("message", "")
|
|
310
|
+
output.print(f"[{event_type}] {message}")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@app.command("delete")
|
|
314
|
+
def session_delete(
|
|
315
|
+
session_id: str = typer.Argument(..., help="Session ID to delete"),
|
|
316
|
+
confirm: bool = typer.Option(
|
|
317
|
+
False,
|
|
318
|
+
"--yes",
|
|
319
|
+
"-y",
|
|
320
|
+
help="Skip confirmation",
|
|
321
|
+
),
|
|
322
|
+
):
|
|
323
|
+
"""Delete a session."""
|
|
324
|
+
output = get_output_controller()
|
|
325
|
+
manager = get_session_manager()
|
|
326
|
+
|
|
327
|
+
session = manager.get(session_id)
|
|
328
|
+
|
|
329
|
+
if not session:
|
|
330
|
+
output.print_error(f"Session not found: {session_id}")
|
|
331
|
+
raise typer.Exit(1)
|
|
332
|
+
|
|
333
|
+
if not confirm:
|
|
334
|
+
confirmed = typer.confirm(f"Delete session {session_id}?")
|
|
335
|
+
if not confirmed:
|
|
336
|
+
output.print_info("Cancelled")
|
|
337
|
+
raise typer.Exit(0)
|
|
338
|
+
|
|
339
|
+
deleted = manager.delete(session_id)
|
|
340
|
+
|
|
341
|
+
if output.is_json_mode:
|
|
342
|
+
output.print_json({"deleted": deleted, "session_id": session_id})
|
|
343
|
+
else:
|
|
344
|
+
if deleted:
|
|
345
|
+
output.print_success(f"Deleted session: {session_id}")
|
|
346
|
+
else:
|
|
347
|
+
output.print_error(f"Failed to delete session: {session_id}")
|
|
348
|
+
raise typer.Exit(1)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@app.command("export")
|
|
352
|
+
def session_export(
|
|
353
|
+
session_id: str = typer.Argument(..., help="Session ID to export"),
|
|
354
|
+
format: str = typer.Option(
|
|
355
|
+
"md",
|
|
356
|
+
"--format",
|
|
357
|
+
"-f",
|
|
358
|
+
help="Export format: md or json",
|
|
359
|
+
),
|
|
360
|
+
output_file: Optional[str] = typer.Option(
|
|
361
|
+
None,
|
|
362
|
+
"--output",
|
|
363
|
+
"-o",
|
|
364
|
+
help="Output file path",
|
|
365
|
+
),
|
|
366
|
+
):
|
|
367
|
+
"""Export a session."""
|
|
368
|
+
output = get_output_controller()
|
|
369
|
+
manager = get_session_manager()
|
|
370
|
+
|
|
371
|
+
content = manager.export(session_id, format=format)
|
|
372
|
+
|
|
373
|
+
if content is None:
|
|
374
|
+
output.print_error(f"Session not found: {session_id}")
|
|
375
|
+
raise typer.Exit(1)
|
|
376
|
+
|
|
377
|
+
if output_file:
|
|
378
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
379
|
+
f.write(content)
|
|
380
|
+
output.print_success(f"Exported to: {output_file}")
|
|
381
|
+
else:
|
|
382
|
+
print(content)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
@app.command("show")
|
|
386
|
+
def session_show(
|
|
387
|
+
session_id: str = typer.Argument(..., help="Session ID to show"),
|
|
388
|
+
):
|
|
389
|
+
"""Show session details."""
|
|
390
|
+
output = get_output_controller()
|
|
391
|
+
manager = get_session_manager()
|
|
392
|
+
|
|
393
|
+
session = manager.get(session_id)
|
|
394
|
+
|
|
395
|
+
if not session:
|
|
396
|
+
output.print_error(f"Session not found: {session_id}")
|
|
397
|
+
raise typer.Exit(1)
|
|
398
|
+
|
|
399
|
+
if output.is_json_mode:
|
|
400
|
+
output.print_json(session.to_dict())
|
|
401
|
+
return
|
|
402
|
+
|
|
403
|
+
output.print_panel(
|
|
404
|
+
f"Session ID: {session.session_id}\n"
|
|
405
|
+
f"Name: {session.name or '-'}\n"
|
|
406
|
+
f"Run ID: {session.run_id}\n"
|
|
407
|
+
f"Trace ID: {session.trace_id}\n"
|
|
408
|
+
f"Created: {session.created_at.isoformat()}\n"
|
|
409
|
+
f"Updated: {session.updated_at.isoformat()}\n"
|
|
410
|
+
f"Status: {session.status}\n"
|
|
411
|
+
f"Events: {session.event_count}\n"
|
|
412
|
+
f"Workspace: {session.workspace or '-'}",
|
|
413
|
+
title="Session Details"
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@app.command("import")
|
|
418
|
+
def session_import(
|
|
419
|
+
input_file: str = typer.Argument(..., help="Session file to import (JSON format)"),
|
|
420
|
+
):
|
|
421
|
+
"""Import a session from a file."""
|
|
422
|
+
import json
|
|
423
|
+
|
|
424
|
+
output = get_output_controller()
|
|
425
|
+
|
|
426
|
+
try:
|
|
427
|
+
with open(input_file, "r", encoding="utf-8") as f:
|
|
428
|
+
data = json.load(f)
|
|
429
|
+
except FileNotFoundError:
|
|
430
|
+
output.print_error(f"File not found: {input_file}")
|
|
431
|
+
raise typer.Exit(1)
|
|
432
|
+
except json.JSONDecodeError as e:
|
|
433
|
+
output.print_error(f"Invalid JSON: {e}")
|
|
434
|
+
raise typer.Exit(1)
|
|
435
|
+
|
|
436
|
+
# Use InteractiveCore to import
|
|
437
|
+
try:
|
|
438
|
+
from praisonai.cli.interactive import InteractiveCore
|
|
439
|
+
|
|
440
|
+
core = InteractiveCore()
|
|
441
|
+
session_id = core.import_session(data)
|
|
442
|
+
|
|
443
|
+
if output.is_json_mode:
|
|
444
|
+
output.print_json({"imported": True, "session_id": session_id})
|
|
445
|
+
else:
|
|
446
|
+
output.print_success(f"Imported session: {session_id}")
|
|
447
|
+
|
|
448
|
+
except Exception as e:
|
|
449
|
+
output.print_error(f"Import failed: {e}")
|
|
450
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Setup command group for PraisonAI CLI.
|
|
3
|
+
|
|
4
|
+
Provides interactive onboarding and configuration wizard.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
import typer
|
|
12
|
+
|
|
13
|
+
from ..output.console import get_output_controller
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(help="Interactive onboarding / configuration wizard")
|
|
16
|
+
|
|
17
|
+
# Default PRAISON_HOME directory
|
|
18
|
+
def get_praison_home() -> Path:
|
|
19
|
+
"""Get the PraisonAI home directory."""
|
|
20
|
+
home = os.getenv("PRAISONAI_HOME")
|
|
21
|
+
if home:
|
|
22
|
+
return Path(home)
|
|
23
|
+
return Path.home() / ".praisonai"
|
|
24
|
+
|
|
25
|
+
PRAISON_HOME = get_praison_home()
|
|
26
|
+
ENV_FILE = PRAISON_HOME / ".env"
|
|
27
|
+
|
|
28
|
+
# Provider configurations
|
|
29
|
+
PROVIDERS = {
|
|
30
|
+
"1": ("openai", "OPENAI_API_KEY", "gpt-4o-mini"),
|
|
31
|
+
"2": ("anthropic", "ANTHROPIC_API_KEY", "claude-3-5-sonnet-latest"),
|
|
32
|
+
"3": ("google", "GEMINI_API_KEY", "gemini-2.0-flash"),
|
|
33
|
+
"4": ("ollama", None, "llama3.2"),
|
|
34
|
+
"5": ("custom", None, None),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
PROVIDER_NAMES = {
|
|
38
|
+
"openai": ("OpenAI", "OPENAI_API_KEY", "gpt-4o-mini"),
|
|
39
|
+
"anthropic": ("Anthropic", "ANTHROPIC_API_KEY", "claude-3-5-sonnet-latest"),
|
|
40
|
+
"google": ("Google", "GEMINI_API_KEY", "gemini-2.0-flash"),
|
|
41
|
+
"ollama": ("Ollama", None, "llama3.2"),
|
|
42
|
+
"custom": ("Custom", None, None),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _run_setup(
|
|
47
|
+
non_interactive: bool = False,
|
|
48
|
+
provider: Optional[str] = None,
|
|
49
|
+
api_key: Optional[str] = None,
|
|
50
|
+
model: Optional[str] = None,
|
|
51
|
+
) -> int:
|
|
52
|
+
"""Run the setup wizard."""
|
|
53
|
+
try:
|
|
54
|
+
from praisonai.cli.features.setup.handler import SetupHandler
|
|
55
|
+
handler = SetupHandler()
|
|
56
|
+
return handler.execute(
|
|
57
|
+
non_interactive=non_interactive,
|
|
58
|
+
provider=provider,
|
|
59
|
+
api_key=api_key,
|
|
60
|
+
model=model
|
|
61
|
+
)
|
|
62
|
+
except ImportError as e:
|
|
63
|
+
output = get_output_controller()
|
|
64
|
+
output.print_error(f"Setup module not available: {e}")
|
|
65
|
+
return 4
|
|
66
|
+
except Exception as e:
|
|
67
|
+
output = get_output_controller()
|
|
68
|
+
output.print_error(f"Setup error: {e}")
|
|
69
|
+
return 1
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@app.callback(invoke_without_command=True)
|
|
73
|
+
def setup_callback(
|
|
74
|
+
ctx: typer.Context,
|
|
75
|
+
non_interactive: bool = typer.Option(False, "--non-interactive", help="Run in non-interactive mode"),
|
|
76
|
+
provider: Optional[str] = typer.Option(None, "--provider", help="LLM provider (openai, anthropic, google, ollama, custom)"),
|
|
77
|
+
api_key: Optional[str] = typer.Option(None, "--api-key", help="API key for the provider"),
|
|
78
|
+
model: Optional[str] = typer.Option(None, "--model", help="Default model to use"),
|
|
79
|
+
):
|
|
80
|
+
"""Run the onboarding wizard (idempotent — safe to re-run)."""
|
|
81
|
+
if ctx.invoked_subcommand:
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
exit_code = _run_setup(
|
|
85
|
+
non_interactive=non_interactive,
|
|
86
|
+
provider=provider,
|
|
87
|
+
api_key=api_key,
|
|
88
|
+
model=model
|
|
89
|
+
)
|
|
90
|
+
raise typer.Exit(exit_code)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@app.command("wizard")
|
|
94
|
+
def setup_wizard(
|
|
95
|
+
provider: Optional[str] = typer.Option(None, "--provider", help="LLM provider"),
|
|
96
|
+
api_key: Optional[str] = typer.Option(None, "--api-key", help="API key"),
|
|
97
|
+
model: Optional[str] = typer.Option(None, "--model", help="Default model"),
|
|
98
|
+
):
|
|
99
|
+
"""Run the interactive setup wizard."""
|
|
100
|
+
exit_code = _run_setup(
|
|
101
|
+
non_interactive=False,
|
|
102
|
+
provider=provider,
|
|
103
|
+
api_key=api_key,
|
|
104
|
+
model=model
|
|
105
|
+
)
|
|
106
|
+
raise typer.Exit(exit_code)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@app.command("config")
|
|
110
|
+
def setup_config(
|
|
111
|
+
show: bool = typer.Option(False, "--show", help="Show current configuration"),
|
|
112
|
+
edit: bool = typer.Option(False, "--edit", help="Edit configuration file"),
|
|
113
|
+
):
|
|
114
|
+
"""Manage setup configuration."""
|
|
115
|
+
output = get_output_controller()
|
|
116
|
+
|
|
117
|
+
if show:
|
|
118
|
+
if ENV_FILE.exists():
|
|
119
|
+
output.console.print(f"[bold]Configuration at {ENV_FILE}:[/bold]")
|
|
120
|
+
content = ENV_FILE.read_text()
|
|
121
|
+
# Don't show actual API keys for security
|
|
122
|
+
lines = []
|
|
123
|
+
for line in content.split('\n'):
|
|
124
|
+
if '=' in line and any(key in line for key in ['API_KEY', 'TOKEN', 'SECRET']):
|
|
125
|
+
key, _ = line.split('=', 1)
|
|
126
|
+
lines.append(f"{key}=***")
|
|
127
|
+
else:
|
|
128
|
+
lines.append(line)
|
|
129
|
+
output.console.print('\n'.join(lines))
|
|
130
|
+
else:
|
|
131
|
+
output.print_warning(f"No configuration found at {ENV_FILE}")
|
|
132
|
+
output.console.print("Run [cyan]praisonai setup[/cyan] to create one.")
|
|
133
|
+
|
|
134
|
+
if edit:
|
|
135
|
+
import subprocess
|
|
136
|
+
editor = os.getenv("EDITOR", "nano")
|
|
137
|
+
try:
|
|
138
|
+
subprocess.run([editor, str(ENV_FILE)], check=True)
|
|
139
|
+
except subprocess.CalledProcessError:
|
|
140
|
+
output.print_error(f"Failed to open editor: {editor}")
|
|
141
|
+
except FileNotFoundError:
|
|
142
|
+
output.print_error(f"Editor not found: {editor}")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@app.command("reset")
|
|
146
|
+
def setup_reset(
|
|
147
|
+
force: bool = typer.Option(False, "--force", help="Skip confirmation"),
|
|
148
|
+
):
|
|
149
|
+
"""Reset setup configuration."""
|
|
150
|
+
output = get_output_controller()
|
|
151
|
+
|
|
152
|
+
praison_home = get_praison_home()
|
|
153
|
+
env_file = praison_home / ".env"
|
|
154
|
+
config_file = praison_home / "config.yaml"
|
|
155
|
+
files_to_remove = [path for path in (env_file, config_file) if path.exists()]
|
|
156
|
+
|
|
157
|
+
if not files_to_remove:
|
|
158
|
+
output.print_info("No setup configuration to reset.")
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
if not force:
|
|
162
|
+
confirm = typer.confirm(f"Reset configuration at {praison_home}?")
|
|
163
|
+
if not confirm:
|
|
164
|
+
output.print_info("Reset cancelled.")
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
for path in files_to_remove:
|
|
169
|
+
path.unlink()
|
|
170
|
+
output.print_success("Configuration reset successfully.")
|
|
171
|
+
output.console.print("Run [cyan]praisonai setup[/cyan] to configure again.")
|
|
172
|
+
except Exception as e:
|
|
173
|
+
output.print_error(f"Failed to reset configuration: {e}")
|
|
174
|
+
raise typer.Exit(1)
|