subactor-shell 0.2.2__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. {subactor_shell-0.2.2/src/subactor_shell.egg-info → subactor_shell-0.2.3}/PKG-INFO +78 -8
  2. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/README.md +77 -7
  3. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/pyproject.toml +1 -1
  4. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/__init__.py +1 -1
  5. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/app.py +248 -5
  6. subactor_shell-0.2.3/src/subactor_shell/auth.py +110 -0
  7. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/catalog.py +79 -0
  8. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/config.py +119 -0
  9. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/connectors.py +20 -4
  10. subactor_shell-0.2.3/src/subactor_shell/fleet_status.py +221 -0
  11. subactor_shell-0.2.3/src/subactor_shell/interaction.py +56 -0
  12. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/operations.py +169 -2
  13. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/repl.py +168 -80
  14. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/store.py +28 -0
  15. subactor_shell-0.2.3/src/subactor_shell/surface.py +175 -0
  16. subactor_shell-0.2.3/src/subactor_shell/system_config.py +83 -0
  17. {subactor_shell-0.2.2 → subactor_shell-0.2.3/src/subactor_shell.egg-info}/PKG-INFO +78 -8
  18. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell.egg-info/SOURCES.txt +11 -0
  19. subactor_shell-0.2.3/tests/test_auth.py +64 -0
  20. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_canonical_cli.py +37 -0
  21. subactor_shell-0.2.3/tests/test_fleet_status.py +108 -0
  22. subactor_shell-0.2.3/tests/test_installer.py +54 -0
  23. subactor_shell-0.2.3/tests/test_interaction.py +66 -0
  24. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_operations.py +64 -0
  25. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_shell_integration.py +36 -0
  26. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_store.py +19 -0
  27. subactor_shell-0.2.3/tests/test_surface.py +96 -0
  28. subactor_shell-0.2.3/tests/test_system_config.py +102 -0
  29. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/LICENSE +0 -0
  30. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/setup.cfg +0 -0
  31. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/__main__.py +0 -0
  32. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/acp_agent.py +0 -0
  33. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/artifacts.py +0 -0
  34. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/chat.py +0 -0
  35. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/compiler.py +0 -0
  36. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/context_builder.py +0 -0
  37. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/control.py +0 -0
  38. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/control_env.py +0 -0
  39. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/intent_ir.py +0 -0
  40. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/models.py +0 -0
  41. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/orchestration.py +0 -0
  42. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/policy.py +0 -0
  43. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/providers/__init__.py +0 -0
  44. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/providers/anthropic.py +0 -0
  45. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/providers/base.py +0 -0
  46. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/providers/mock.py +0 -0
  47. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/providers/openai_compat.py +0 -0
  48. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/providers/subactor_control.py +0 -0
  49. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/redaction.py +0 -0
  50. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/routing.py +0 -0
  51. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/secret_refs.py +0 -0
  52. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/terminal.py +0 -0
  53. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/token_budget.py +0 -0
  54. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell/vault.py +0 -0
  55. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell.egg-info/dependency_links.txt +0 -0
  56. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell.egg-info/entry_points.txt +0 -0
  57. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell.egg-info/requires.txt +0 -0
  58. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/src/subactor_shell.egg-info/top_level.txt +0 -0
  59. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_acp_agent.py +0 -0
  60. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_acp_extensions.py +0 -0
  61. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_app_errors.py +0 -0
  62. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_artifact_selection.py +0 -0
  63. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_chat.py +0 -0
  64. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_context_budget.py +0 -0
  65. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_control.py +0 -0
  66. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_control_env.py +0 -0
  67. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_orchestration.py +0 -0
  68. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_providers.py +0 -0
  69. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_redaction.py +0 -0
  70. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_router_local_parser.py +0 -0
  71. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_structured_output.py +0 -0
  72. {subactor_shell-0.2.2 → subactor_shell-0.2.3}/tests/test_vault.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: subactor-shell
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Subactor terminal shell with IntentIR orchestration, governed Control integration, persistent sessions and ACP stdio.
5
5
  Author: Subactor community
6
6
  Author-email: Tom Sapletta <tom@sapletta.com>
@@ -25,18 +25,16 @@ Dynamic: license-file
25
25
 
26
26
  ## AI Cost Tracking
27
27
 
28
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.2-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
29
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.06-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-9.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
28
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.3-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
29
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.38-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-29.7h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
30
30
 
31
- - 🤖 **LLM usage:** $0.0595 (3 commits)
32
- - 👤 **Human dev:** ~$953 (9.5h @ $100/h, 30min dedup)
31
+ - 🤖 **LLM usage:** $0.3788 (7 commits)
32
+ - 👤 **Human dev:** ~$2968 (29.7h @ $100/h, 30min dedup)
33
33
 
34
- Generated on 2026-08-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
34
+ Generated on 2026-09-03 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
35
35
 
36
36
  ---
37
37
 
38
-
39
-
40
38
  Subactor Shell jest trwałą warstwą rozmowy i orkiestracji dla terminala oraz klientów ACP. Wersja 0.2.2 integruje pytania operacyjne z zarządzanym CLI Subactora, poprawia obsługę kończenia sesji i nie przejmuje nazwy Founder Chat.
41
39
 
42
40
  ```text
@@ -70,6 +68,40 @@ Pełny transcript nadal jest zapisywany w SQLite, ale provider rozmowy dostaje t
70
68
 
71
69
  ## Instalacja
72
70
 
71
+ ### Jednolinijkowy instalator (zalecane)
72
+
73
+ Mac / Linux:
74
+
75
+ ```bash
76
+ curl -fsSL https://subactor.github.io/shell/install.sh | sh
77
+ ```
78
+
79
+ Windows:
80
+
81
+ ```powershell
82
+ powershell -ExecutionPolicy ByPass -c "irm https://subactor.github.io/shell/install.ps1 | iex"
83
+ ```
84
+
85
+ Instalator domyślnie pobiera metadane z
86
+ [`subactor.github.io/shell`](https://subactor.github.io/shell/) i w razie
87
+ problemu przechodzi na
88
+ [GitHub Releases](https://github.com/subactor/shell/releases). Aby wymusić
89
+ GitHub Releases:
90
+
91
+ ```bash
92
+ curl -fsSL https://subactor.github.io/shell/install.sh | SUBACTOR_SHELL_INSTALLER_USE_GITHUB_IO=false sh
93
+ ```
94
+
95
+ ```powershell
96
+ $env:SUBACTOR_SHELL_INSTALLER_USE_GITHUB_IO='false'; irm https://subactor.github.io/shell/install.ps1 | iex
97
+ ```
98
+
99
+ Wymagania: Python 3.11+. Instalator tworzy izolowane środowisko w
100
+ `~/.local/share/subactor-shell/venv` i linkuje `subactor-shell` do
101
+ `~/.local/bin`.
102
+
103
+ ### Z wheel lub źródeł
104
+
73
105
  Pakiet instaluje wyłącznie polecenie `subactor-shell`. Nazwa `subactor` jest
74
106
  zarezerwowana dla Founder Chat dostarczanego przez Platformę, dzięki czemu
75
107
  `subactor chat` zachowuje swój interfejs, pełną diagnostykę i kontrakt sesji.
@@ -131,6 +163,44 @@ subactor-shell one 'pokaż sesje'
131
163
 
132
164
  Przy znanym intencie read-only wynik może zostać wykonany lokalnie bez wywołania providera rozmowy.
133
165
 
166
+ ## Scope node, konto GitHub i źródła konfiguracji
167
+
168
+ Shell pobiera wspólną, pozbawioną sekretów projekcję z usługi
169
+ `subactor/config`. Po uruchomieniu jej lokalnego endpointu można sprawdzić
170
+ bieżący komputer, osobno scope kodu i danych, ustawienia konta oraz federacyjny
171
+ katalog właścicieli konfiguracji:
172
+
173
+ ```bash
174
+ subactor-shell scope --node current
175
+ subactor-shell scope --node current --kind code --json
176
+ subactor-shell scope --node current --kind data
177
+ subactor-shell account github
178
+ subactor-shell account github --json
179
+ subactor-shell source list
180
+ subactor-shell source list --provides configuration.propose --json
181
+ subactor-shell source show credential-vault
182
+ subactor-shell resolve credential.metadata
183
+ ```
184
+
185
+ Endpoint jest jawnie konfigurowany w `~/.config/subactor-shell/config.toml`:
186
+
187
+ ```toml
188
+ [system_config]
189
+ base_url = "http://127.0.0.1:8198"
190
+ timeout_seconds = 5.0
191
+ ```
192
+
193
+ Port `8198` jest kanonicznym bindingiem `subactor/config`; `8098` pozostaje
194
+ zajęty przez lokalny LLM Gateway.
195
+
196
+ Wynik konta pokazuje tylko skonfigurowany podmiot, znormalizowany stan
197
+ uwierzytelnienia oraz bezpieczne lokalizacje ustawień. Token ani surowy wynik
198
+ `gh auth status` nie są zwracane.
199
+
200
+ Resolver zwraca źródła, ich właścicieli i dostępność na bieżącym node. Wynik
201
+ ma `authority=evidence-only`: pełny zakres decyzji Supervisora nie zastępuje
202
+ zewnętrznego grantu wymaganego do mutacji.
203
+
134
204
  ## Routing modeli
135
205
 
136
206
  Minimalna konfiguracja lokalnego parsera OpenAI-compatible:
@@ -3,18 +3,16 @@
3
3
 
4
4
  ## AI Cost Tracking
5
5
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.2-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.06-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-9.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.3-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.38-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-29.7h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $0.0595 (3 commits)
10
- - 👤 **Human dev:** ~$953 (9.5h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $0.3788 (7 commits)
10
+ - 👤 **Human dev:** ~$2968 (29.7h @ $100/h, 30min dedup)
11
11
 
12
- Generated on 2026-08-29 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
12
+ Generated on 2026-09-03 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
13
 
14
14
  ---
15
15
 
16
-
17
-
18
16
  Subactor Shell jest trwałą warstwą rozmowy i orkiestracji dla terminala oraz klientów ACP. Wersja 0.2.2 integruje pytania operacyjne z zarządzanym CLI Subactora, poprawia obsługę kończenia sesji i nie przejmuje nazwy Founder Chat.
19
17
 
20
18
  ```text
@@ -48,6 +46,40 @@ Pełny transcript nadal jest zapisywany w SQLite, ale provider rozmowy dostaje t
48
46
 
49
47
  ## Instalacja
50
48
 
49
+ ### Jednolinijkowy instalator (zalecane)
50
+
51
+ Mac / Linux:
52
+
53
+ ```bash
54
+ curl -fsSL https://subactor.github.io/shell/install.sh | sh
55
+ ```
56
+
57
+ Windows:
58
+
59
+ ```powershell
60
+ powershell -ExecutionPolicy ByPass -c "irm https://subactor.github.io/shell/install.ps1 | iex"
61
+ ```
62
+
63
+ Instalator domyślnie pobiera metadane z
64
+ [`subactor.github.io/shell`](https://subactor.github.io/shell/) i w razie
65
+ problemu przechodzi na
66
+ [GitHub Releases](https://github.com/subactor/shell/releases). Aby wymusić
67
+ GitHub Releases:
68
+
69
+ ```bash
70
+ curl -fsSL https://subactor.github.io/shell/install.sh | SUBACTOR_SHELL_INSTALLER_USE_GITHUB_IO=false sh
71
+ ```
72
+
73
+ ```powershell
74
+ $env:SUBACTOR_SHELL_INSTALLER_USE_GITHUB_IO='false'; irm https://subactor.github.io/shell/install.ps1 | iex
75
+ ```
76
+
77
+ Wymagania: Python 3.11+. Instalator tworzy izolowane środowisko w
78
+ `~/.local/share/subactor-shell/venv` i linkuje `subactor-shell` do
79
+ `~/.local/bin`.
80
+
81
+ ### Z wheel lub źródeł
82
+
51
83
  Pakiet instaluje wyłącznie polecenie `subactor-shell`. Nazwa `subactor` jest
52
84
  zarezerwowana dla Founder Chat dostarczanego przez Platformę, dzięki czemu
53
85
  `subactor chat` zachowuje swój interfejs, pełną diagnostykę i kontrakt sesji.
@@ -109,6 +141,44 @@ subactor-shell one 'pokaż sesje'
109
141
 
110
142
  Przy znanym intencie read-only wynik może zostać wykonany lokalnie bez wywołania providera rozmowy.
111
143
 
144
+ ## Scope node, konto GitHub i źródła konfiguracji
145
+
146
+ Shell pobiera wspólną, pozbawioną sekretów projekcję z usługi
147
+ `subactor/config`. Po uruchomieniu jej lokalnego endpointu można sprawdzić
148
+ bieżący komputer, osobno scope kodu i danych, ustawienia konta oraz federacyjny
149
+ katalog właścicieli konfiguracji:
150
+
151
+ ```bash
152
+ subactor-shell scope --node current
153
+ subactor-shell scope --node current --kind code --json
154
+ subactor-shell scope --node current --kind data
155
+ subactor-shell account github
156
+ subactor-shell account github --json
157
+ subactor-shell source list
158
+ subactor-shell source list --provides configuration.propose --json
159
+ subactor-shell source show credential-vault
160
+ subactor-shell resolve credential.metadata
161
+ ```
162
+
163
+ Endpoint jest jawnie konfigurowany w `~/.config/subactor-shell/config.toml`:
164
+
165
+ ```toml
166
+ [system_config]
167
+ base_url = "http://127.0.0.1:8198"
168
+ timeout_seconds = 5.0
169
+ ```
170
+
171
+ Port `8198` jest kanonicznym bindingiem `subactor/config`; `8098` pozostaje
172
+ zajęty przez lokalny LLM Gateway.
173
+
174
+ Wynik konta pokazuje tylko skonfigurowany podmiot, znormalizowany stan
175
+ uwierzytelnienia oraz bezpieczne lokalizacje ustawień. Token ani surowy wynik
176
+ `gh auth status` nie są zwracane.
177
+
178
+ Resolver zwraca źródła, ich właścicieli i dostępność na bieżącym node. Wynik
179
+ ma `authority=evidence-only`: pełny zakres decyzji Supervisora nie zastępuje
180
+ zewnętrznego grantu wymaganego do mutacji.
181
+
112
182
  ## Routing modeli
113
183
 
114
184
  Minimalna konfiguracja lokalnego parsera OpenAI-compatible:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "subactor-shell"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "Subactor terminal shell with IntentIR orchestration, governed Control integration, persistent sessions and ACP stdio."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """Subactor terminal shell."""
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.2.3"
@@ -4,6 +4,7 @@ import argparse
4
4
  import asyncio
5
5
  import getpass
6
6
  import json
7
+ import os
7
8
  import stat
8
9
  import sys
9
10
  from pathlib import Path
@@ -18,9 +19,12 @@ from .compiler import ExecutionPlan
18
19
  from .config import initialize_layout, load_config
19
20
  from .control import SubactorControlClient
20
21
  from .control_env import apply_control_environment
22
+ from .fleet_status import fetch_fleet_overview, render_fleet_startup_banner
21
23
  from .operations import OperationSettings, OperationsClient, run_operational_command
22
24
  from .repl import ShellRepl
23
25
  from .store import Store
26
+ from .system_config import SystemConfigClient
27
+ from .terminal import terminal_hyperlinks_enabled
24
28
 
25
29
 
26
30
  def build_parser() -> argparse.ArgumentParser:
@@ -48,6 +52,7 @@ def build_parser() -> argparse.ArgumentParser:
48
52
  one.add_argument("--model", help="model dla nowej sesji")
49
53
  one.add_argument("--attach", action="append", type=Path, default=[])
50
54
  one.add_argument("--grant", action="append", default=[], help="jednorazowy grant aliasu sekretu")
55
+ one.add_argument("--json", action="store_true", help="emituj ustrukturyzowaną odpowiedź w formacie JSON")
51
56
 
52
57
  data = sub.add_parser("data", help="zapisuj jawne dane tekstowe i pliki")
53
58
  data_sub = data.add_subparsers(dest="data_command", required=True)
@@ -77,8 +82,12 @@ def build_parser() -> argparse.ArgumentParser:
77
82
  vault_wrap.add_argument("alias")
78
83
  vault_wrap.add_argument("--ttl", default="5m")
79
84
 
80
- sessions = sub.add_parser("sessions", help="lista zapisanych sesji")
81
- sessions.add_argument("--json", action="store_true")
85
+ sessions = sub.add_parser("sessions", help="zarządzanie sesjami (lista, usuwanie, czyszczenie)")
86
+ sessions.add_argument("--json", action="store_true", help="zwróć listę sesji jako JSON")
87
+ sessions.add_argument("--delete", metavar="SESSION_ID", help="usuń wskazaną sesję")
88
+ sessions.add_argument("--prune", action="store_true", help="usuń nieaktywne lub puste sesje")
89
+ sessions.add_argument("--older-than-days", type=int, default=30, help="próg wieku dla --prune (domyślnie 30 dni)")
90
+ sessions.add_argument("--empty-only", action="store_true", help="usuń tylko puste sesje bez wiadomości")
82
91
 
83
92
  export = sub.add_parser("export", help="eksport sesji do JSON")
84
93
  export.add_argument("session")
@@ -108,6 +117,14 @@ def build_parser() -> argparse.ArgumentParser:
108
117
  tickets.add_argument("--project", default="")
109
118
  tickets.add_argument("--text", default="")
110
119
  tickets.add_argument("--json", action="store_true")
120
+ orgs = sub.add_parser("orgs", help="rejestry organizacji (dashboard lub lista zasobu)")
121
+ orgs.add_argument("resource", nargs="?", default="", help="np. organizations, contacts")
122
+ orgs.add_argument("--json", action="store_true")
123
+ projects = sub.add_parser("projects", help="portfolio projektów lub reconciliation")
124
+ projects.add_argument("--recon", action="store_true", help="widok /api/projects/reconciliation")
125
+ projects.add_argument("--json", action="store_true")
126
+ performance = sub.add_parser("performance", aliases=["perf"], help="ranking kosztu i ROI URI Process")
127
+ performance.add_argument("--json", action="store_true")
111
128
  sub.add_parser("health", help="publiczny health Subactor Control")
112
129
  dispatch = sub.add_parser("dispatch", help="rozdysponuj pracę przez Control")
113
130
  dispatch.add_argument("--confirm", default="")
@@ -128,6 +145,25 @@ def build_parser() -> argparse.ArgumentParser:
128
145
  post.add_argument("--confirm", default="")
129
146
  sub.add_parser("endpoints", help="katalog operacyjnego API")
130
147
 
148
+ scope = sub.add_parser("scope", help="scope code i data na wskazanym node")
149
+ scope.add_argument("--node", default="current")
150
+ scope.add_argument("--kind", choices=["all", "code", "data"], default="all")
151
+ scope.add_argument("--json", action="store_true")
152
+ account = sub.add_parser("account", help="scope i ustawienia konta providera")
153
+ account.add_argument("provider", choices=["github"])
154
+ account.add_argument("--json", action="store_true")
155
+ source = sub.add_parser("source", help="federacyjny katalog źródeł konfiguracji")
156
+ source_sub = source.add_subparsers(dest="source_command", required=True)
157
+ source_list = source_sub.add_parser("list", help="lista źródeł konfiguracji")
158
+ source_list.add_argument("--provides", default=None, help="filtr dokładnej zdolności")
159
+ source_list.add_argument("--json", action="store_true")
160
+ source_show = source_sub.add_parser("show", help="szczegóły źródła konfiguracji")
161
+ source_show.add_argument("source_id")
162
+ source_show.add_argument("--json", action="store_true")
163
+ resolve = sub.add_parser("resolve", help="znajdź źródła dla wymaganej zdolności")
164
+ resolve.add_argument("need")
165
+ resolve.add_argument("--json", action="store_true")
166
+
131
167
  receipts = sub.add_parser("receipts", help="przeglądaj krótkie receipts wykonania")
132
168
  receipts_sub = receipts.add_subparsers(dest="receipts_command", required=True)
133
169
  receipts_list = receipts_sub.add_parser("list")
@@ -146,7 +182,27 @@ def build_parser() -> argparse.ArgumentParser:
146
182
  connectors = sub.add_parser("connectors", help="allowlista nazwanych connectorów")
147
183
  connectors.add_argument("--json", action="store_true")
148
184
 
185
+ login = sub.add_parser("login", help="uwierzytelnienie sesji CLI (magic link + PKCE)")
186
+ login.add_argument("email", help="adres e-mail konta SaaS / Subactor")
187
+
188
+ auth = sub.add_parser("auth", help="stan uwierzytelnienia i adres Process Control (:8091)")
189
+ auth.add_argument("--json", action="store_true")
190
+
191
+ backup = sub.add_parser("backup", help="zarządzanie kopiami zapasowymi konta i baz SQLite")
192
+ backup_sub = backup.add_subparsers(dest="backup_command", required=True)
193
+ backup_create = backup_sub.add_parser("create", help="utwórz nowy snapshot baz i manifest SHA-256")
194
+ backup_create.add_argument("--tenant", default="default-tenant", help="identyfikator konta organizacji")
195
+ backup_create.add_argument("--json", action="store_true")
196
+ backup_list = backup_sub.add_parser("list", help="lista dostępnych kopii zapasowych")
197
+ backup_list.add_argument("--json", action="store_true")
198
+ backup_restore = backup_sub.add_parser("restore", help="przywróć bazę z archiwum kopii zapasowej")
199
+ backup_restore.add_argument("archive", help="ścieżka do pliku archiwum .tar.gz")
200
+ backup_restore.add_argument("--target-dir", default=None, help="docelowy katalog przywrócenia")
201
+ backup_restore.add_argument("--dry-run", action="store_true")
202
+
149
203
  sub.add_parser("doctor", help="diagnostyka bez ujawniania sekretów")
204
+ sub.add_parser("fleet", help="stan Pull Requestów, ticketów i floty ekosystemu")
205
+ sub.add_parser("prs", help="otwarte Pull Requesty (subactor, if-uri)")
150
206
  sub.add_parser("acp-agent", help="uruchom agenta ACP v1 po stdio")
151
207
  return parser
152
208
 
@@ -169,11 +225,30 @@ async def _one(chat: ChatService, args: argparse.Namespace, console: Console) ->
169
225
  session = chat.get_or_create_session(args.session, provider=args.provider, model=args.model)
170
226
  for alias in args.grant:
171
227
  chat.grant_secret(alias)
228
+ is_json = bool(getattr(args, "json", False))
229
+ chunks: list[str] = []
172
230
  async for chunk in chat.stream_message(session.id, args.message, attachment_paths=args.attach):
173
- console.print(chunk, end="", markup=False, soft_wrap=True)
231
+ chunks.append(chunk)
232
+ if not is_json:
233
+ console.print(chunk, end="", markup=False, soft_wrap=True)
234
+
235
+ route = chat.store.last_routing_decision(session.id)
236
+ if is_json:
237
+ receipt = None
238
+ receipts = chat.store.list_execution_receipts(session.id)
239
+ if receipts:
240
+ receipt = receipts[0]
241
+ payload = {
242
+ "session_id": session.id,
243
+ "text": "".join(chunks),
244
+ "route": route,
245
+ "receipt": receipt,
246
+ }
247
+ console.print_json(json.dumps(payload, ensure_ascii=False))
248
+ return 0
249
+
174
250
  console.print()
175
251
  console.print(f"[dim]session={session.id}[/dim]")
176
- route = chat.store.last_routing_decision(session.id)
177
252
  if route and bool(chat.config.orchestration.get("show_route", False)):
178
253
  console.print(
179
254
  f"[dim]route={route['route']} intent={route['intent_id']} confidence={route['confidence']:.3f}[/dim]"
@@ -181,6 +256,7 @@ async def _one(chat: ChatService, args: argparse.Namespace, console: Console) ->
181
256
  return 0
182
257
 
183
258
 
259
+
184
260
  def _doctor(config, store: Store, chat: ChatService, console: Console) -> int:
185
261
  table = Table("Test", "Wynik", "Szczegóły")
186
262
  failures = 0
@@ -363,6 +439,77 @@ def _receipts_command(store: Store, args: argparse.Namespace, console: Console)
363
439
  return 0
364
440
 
365
441
 
442
+ def _system_config_command(client: SystemConfigClient, args: argparse.Namespace, console: Console) -> int:
443
+ if args.command == "scope":
444
+ payload = client.node_scope(args.node)
445
+ if args.json:
446
+ console.print_json(json.dumps(payload, ensure_ascii=False))
447
+ return 0
448
+ console.print(f"Node: [cyan]{payload['node']['id']}[/cyan] ({payload['node']['hostname']})")
449
+ if args.kind != "data":
450
+ table = Table("Repo", "Branch", "URI", "Dirty")
451
+ for item in payload["scope"]["code"]["repositories"]:
452
+ table.add_row(str(item["id"]), str(item["branch"]), str(item["uri"]), "tak" if item["dirty"] else "nie")
453
+ console.print(table)
454
+ if args.kind != "code":
455
+ table = Table("Dane", "Rodzaj", "Dostęp", "URI")
456
+ for item in payload["scope"]["data"]["resources"]:
457
+ table.add_row(str(item["id"]), str(item["kind"]), str(item["access"]), str(item["uri"]))
458
+ console.print(table)
459
+ return 0
460
+
461
+ if args.command == "account":
462
+ payload = client.account(args.provider)
463
+ console.print_json(json.dumps(payload, ensure_ascii=False)) if args.json else None
464
+ if args.json:
465
+ return 0
466
+ account = payload["account"]
467
+ authentication = payload["authentication"]
468
+ console.print(f"Konto: [cyan]{account['provider']}:{account['configuredSubject']}[/cyan]")
469
+ console.print(f"Uwierzytelnienie: {authentication['state']} · effective scope: {authentication['effectiveScope']}")
470
+ table = Table("Ustawienie", "Status", "Lokalizacja")
471
+ for item in payload["settingsLocations"]:
472
+ table.add_row(str(item["kind"]), str(item["status"]), str(item["uri"]))
473
+ console.print(table)
474
+ console.print(f"Declared scope: code={len(payload['scope']['code'])}, data={len(payload['scope']['data'])}")
475
+ return 0
476
+
477
+ if args.command == "source":
478
+ if args.source_command == "list":
479
+ payload = client.sources(provides=args.provides)
480
+ if args.json:
481
+ console.print_json(json.dumps(payload, ensure_ascii=False))
482
+ return 0
483
+ table = Table("Źródło", "Rodzaj", "Dostępność", "Zdolności")
484
+ for item in payload:
485
+ table.add_row(str(item["id"]), str(item["kind"]), str(item["availability"]["state"]), ", ".join(item["provides"]))
486
+ console.print(table)
487
+ return 0
488
+ payload = client.source(args.source_id)
489
+ if args.json:
490
+ console.print_json(json.dumps(payload, ensure_ascii=False))
491
+ return 0
492
+ console.print(f"Źródło: [cyan]{payload['id']}[/cyan] · {payload['availability']['state']}")
493
+ console.print(f"Właściciele: {', '.join(payload['owners'])}")
494
+ console.print(f"Zdolności: {', '.join(payload['provides'])}")
495
+ console.print(f"Authority: {payload['authority']}")
496
+ return 0
497
+
498
+ if args.command == "resolve":
499
+ payload = client.resolve(args.need)
500
+ if args.json:
501
+ console.print_json(json.dumps(payload, ensure_ascii=False))
502
+ else:
503
+ table = Table("Potrzeba", "Źródło", "Dostępność", "Authority")
504
+ for item in payload["matchedSources"]:
505
+ table.add_row(payload["need"], str(item["id"]), str(item["availability"]["state"]), payload["authority"])
506
+ console.print(table)
507
+ console.print(f"Mutation mode: {payload['control']['mutationMode']}")
508
+ return 0 if payload["matchedSources"] else 2
509
+
510
+ raise RuntimeError("Nieobsługiwana komenda konfiguracji")
511
+
512
+
366
513
  def main(argv: list[str] | None = None) -> None:
367
514
  parser = build_parser()
368
515
  args = parser.parse_args(argv)
@@ -382,7 +529,11 @@ def main(argv: list[str] | None = None) -> None:
382
529
 
383
530
  apply_control_environment()
384
531
 
385
- operational = {"status", "tickets", "health", "dispatch", "uri", "api", "get", "post", "endpoints"}
532
+ if command in {"scope", "account", "source", "resolve"}:
533
+ config = load_config(args.config, args.data_dir, create=False)
534
+ raise SystemExit(_system_config_command(SystemConfigClient(config.system_config), args, console))
535
+
536
+ operational = {"status", "tickets", "orgs", "projects", "performance", "perf", "health", "dispatch", "uri", "api", "get", "post", "endpoints"}
386
537
  if command in operational or (command == "plans" and args.plans_command == "remote"):
387
538
  client = OperationsClient(OperationSettings.from_environment())
388
539
  raise SystemExit(run_operational_command(args, console, client))
@@ -400,6 +551,17 @@ def main(argv: list[str] | None = None) -> None:
400
551
  elif command == "vault":
401
552
  raise SystemExit(_vault_command(chat, args, console))
402
553
  elif command == "sessions":
554
+ if getattr(args, "delete", None):
555
+ removed = store.delete_session(args.delete)
556
+ console.print(f"Usunięto sesję [cyan]{args.delete}[/cyan]." if removed else f"Nie znaleziono sesji {args.delete}.")
557
+ raise SystemExit(0 if removed else 1)
558
+ if getattr(args, "prune", False):
559
+ count = store.prune_sessions(
560
+ older_than_days=args.older_than_days,
561
+ empty_only=args.empty_only,
562
+ )
563
+ console.print(f"Wyczyszczono [cyan]{count}[/cyan] sesji.")
564
+ raise SystemExit(0)
403
565
  sessions = store.list_sessions()
404
566
  if args.json:
405
567
  console.print_json(
@@ -423,6 +585,7 @@ def main(argv: list[str] | None = None) -> None:
423
585
  for item in sessions:
424
586
  table.add_row(item.id, item.name, item.provider, item.model, item.updated_at)
425
587
  console.print(table)
588
+
426
589
  elif command == "export":
427
590
  payload = store.export_session(args.session)
428
591
  output = args.output.expanduser()
@@ -464,8 +627,88 @@ def main(argv: list[str] | None = None) -> None:
464
627
  for item in chat.orchestration.registry.list():
465
628
  table.add_row(item.name, item.kind, item.effect, ", ".join(item.allowed_operations))
466
629
  console.print(table)
630
+ elif command == "login":
631
+ from .auth import login_with_email, mask_email
632
+ control_url = getattr(config, "control_url", "http://192.168.188.212:8091")
633
+ try:
634
+ res = login_with_email(args.email, control_url=control_url)
635
+ console.print(f"[green]✓[/green] Wysłano link zatwierdzający na adres [cyan]{res.get('masked_email', args.email)}[/cyan].")
636
+ console.print("[dim]Kliknij link w wiadomości e-mail, aby aktywować sesję CLI.[/dim]")
637
+ return
638
+ except Exception as err:
639
+ Console(stderr=True).print(f"[red]Błąd logowania:[/red] {err}")
640
+ raise SystemExit(1)
641
+ elif command == "auth":
642
+ from .auth import probe_auth_session, default_session_path
643
+ control_url = getattr(config, "control_url", "http://192.168.188.212:8091")
644
+ bearer_token = os.environ.get("SUBACTOR_ADMIN_TOKEN")
645
+ probe = probe_auth_session(control_url, bearer_token)
646
+ if getattr(args, "json", False):
647
+ console.print_json(json.dumps(probe, ensure_ascii=False))
648
+ return
649
+ console.print(f"[bold] Uwierzytelnianie Process Control / SaaS[/bold]")
650
+ console.print(f" [dim]Adres usługi:[/dim] [cyan]{control_url}[/cyan]")
651
+ console.print(f" [dim]Status sesji:[/dim] {'[green]Uwierzytelniony[/green]' if probe.get('authenticated') else '[yellow]Sesja anonimowa / lokalna[/yellow]'}")
652
+ if probe.get("identity"):
653
+ console.print(f" [dim]Tożsamość:[/dim] [bold]{probe['identity']}[/bold]")
654
+ console.print(f" [dim]Plik sesji:[/dim] {default_session_path()}")
655
+ return
656
+ elif command == "backup":
657
+ import httpx
658
+ control_url = getattr(config, "control_url", "http://192.168.188.212:8091")
659
+ bearer_token = os.environ.get("SUBACTOR_ADMIN_TOKEN")
660
+ headers = {"Authorization": f"Bearer {bearer_token}"} if bearer_token else {}
661
+
662
+ if args.backup_command == "create":
663
+ with httpx.Client(timeout=30.0) as client:
664
+ resp = client.post(f"{control_url.rstrip('/')}/api/account/backup/export", json={"tenant_id": args.tenant}, headers=headers)
665
+ if resp.status_code != 200:
666
+ raise RuntimeError(f"Błąd tworzenia backupu: {resp.text}")
667
+ data = resp.json()
668
+ if getattr(args, "json", False):
669
+ console.print_json(json.dumps(data, ensure_ascii=False))
670
+ return
671
+ console.print(f"[green]✓[/green] Utworzono kopię zapasową konta [bold]{data.get('tenant_id')}[/bold]")
672
+ console.print(f" [dim]Archiwum:[/dim] [cyan]{data.get('archive_path')}[/cyan]")
673
+ console.print(f" [dim]Rozmiar:[/dim] {data.get('size_bytes')} bajtów ({data.get('files_count')} plików)")
674
+ console.print(f" [dim]SHA-256:[/dim] {data.get('sha256')[:16]}...")
675
+ return
676
+
677
+ if args.backup_command == "list":
678
+ with httpx.Client(timeout=10.0) as client:
679
+ resp = client.get(f"{control_url.rstrip('/')}/api/account/backup/list", headers=headers)
680
+ if resp.status_code != 200:
681
+ raise RuntimeError(f"Błąd pobierania listy backupów: {resp.text}")
682
+ data = resp.json()
683
+ if getattr(args, "json", False):
684
+ console.print_json(json.dumps(data, ensure_ascii=False))
685
+ return
686
+ table = Table("Plik kopii zapasowej", "Rozmiar", "Data utworzenia")
687
+ for b in data.get("backups", []):
688
+ table.add_row(b.get("file_name", ""), f"{b.get('size_bytes', 0)} B", b.get("created_at", ""))
689
+ console.print(table)
690
+ return
691
+
692
+ if args.backup_command == "restore":
693
+ with httpx.Client(timeout=30.0) as client:
694
+ resp = client.post(
695
+ f"{control_url.rstrip('/')}/api/account/backup/restore",
696
+ json={"archive_path": args.archive, "target_dir": args.target_dir, "dry_run": args.dry_run},
697
+ headers=headers,
698
+ )
699
+ if resp.status_code != 200:
700
+ raise RuntimeError(f"Błąd przywracania backupu: {resp.text}")
701
+ data = resp.json()
702
+ console.print(f"[green]✓[/green] Przywrócono dane z kopii [bold]{data.get('backup_id')}[/bold]")
703
+ console.print(f" [dim]Zweryfikowano plików:[/dim] {len(data.get('verified_files', []))}")
704
+ return
467
705
  elif command == "doctor":
468
706
  raise SystemExit(_doctor(config, store, chat, console))
707
+ elif command in {"fleet", "prs"}:
708
+ hyperlinks = terminal_hyperlinks_enabled(is_terminal=console.is_terminal)
709
+ overview = fetch_fleet_overview()
710
+ render_fleet_startup_banner(console, overview, hyperlinks=hyperlinks)
711
+ return
469
712
  elif command == "acp-agent":
470
713
  asyncio.run(AcpAgent(chat).run_stdio())
471
714
  else: