aline-ai 0.5.13__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aline_ai-0.5.13.dist-info → aline_ai-0.6.0.dist-info}/METADATA +1 -1
- {aline_ai-0.5.13.dist-info → aline_ai-0.6.0.dist-info}/RECORD +16 -16
- realign/__init__.py +1 -1
- realign/cli.py +249 -0
- realign/commands/auth.py +101 -0
- realign/commands/import_shares.py +6 -0
- realign/commands/watcher.py +8 -0
- realign/commands/worker.py +8 -0
- realign/dashboard/widgets/events_table.py +44 -5
- realign/dashboard/widgets/sessions_table.py +76 -16
- realign/watcher_daemon.py +11 -0
- realign/worker_daemon.py +11 -0
- {aline_ai-0.5.13.dist-info → aline_ai-0.6.0.dist-info}/WHEEL +0 -0
- {aline_ai-0.5.13.dist-info → aline_ai-0.6.0.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.5.13.dist-info → aline_ai-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.5.13.dist-info → aline_ai-0.6.0.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
aline_ai-0.
|
|
2
|
-
realign/__init__.py,sha256=
|
|
1
|
+
aline_ai-0.6.0.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
|
|
2
|
+
realign/__init__.py,sha256=a8wH_-YI_n2n6He2CL8jdy4zSUj9pxtT6cz9iNZgp24,1623
|
|
3
3
|
realign/auth.py,sha256=63fdy-KsNoLZ9A6X0Mz_v-0tQOXN_1XncXBGBlEoXqE,16030
|
|
4
4
|
realign/claude_detector.py,sha256=ZLSJacMo6zzQclXByABKA70UNpstxqIv3fPGqdpA934,2792
|
|
5
|
-
realign/cli.py,sha256=
|
|
5
|
+
realign/cli.py,sha256=2BhDitD5IYUPJRGtfOXv_m0r7ZsiPU9iGtkp2pfA59M,41637
|
|
6
6
|
realign/codex_detector.py,sha256=N9ulgMgvTzDfXE4s4vLd6OoS0hT7R6h2bDFFXWa-2hE,4183
|
|
7
7
|
realign/config.py,sha256=d8HQ6v1xju6cjNGbR7LlfHaMyvFPcMDofhGbepxpQq8,11634
|
|
8
8
|
realign/context.py,sha256=8hzgNOg-7_eMW22wt7OM5H9IsmMveKXCv0epG7E0G7w,13917
|
|
@@ -14,9 +14,9 @@ realign/mcp_server.py,sha256=LWiQ2qukYoNLsoV2ID2f0vF9jkJlBvB587HpM5jymgE,10193
|
|
|
14
14
|
realign/mcp_watcher.py,sha256=aK4jWStv7CoCroS4tXFHgZ_y_-q4QDjrpWgm4DxcEj4,1260
|
|
15
15
|
realign/redactor.py,sha256=Zsoi5HfYak2yPmck20JArhm-1cPSB78IdkBJiNVXfrc,17096
|
|
16
16
|
realign/watcher_core.py,sha256=NNn_xlm50Ybb60--DrF9dyvzGgJ4ENQeAUbZsH7w4to,106555
|
|
17
|
-
realign/watcher_daemon.py,sha256=
|
|
17
|
+
realign/watcher_daemon.py,sha256=OHUQ9P1LlagKJHfrf6uRnzO-zDtBRXIxt8ydMFHf5S8,3475
|
|
18
18
|
realign/worker_core.py,sha256=-GOItHE0vzExB8LZK6KeHx4tt_mIqtCoUljOtEg2x8A,10105
|
|
19
|
-
realign/worker_daemon.py,sha256=
|
|
19
|
+
realign/worker_daemon.py,sha256=X7Xyjw_u6m6KG4E84nx0HpDFw4cWMv8ja1G8btc9PiM,3957
|
|
20
20
|
realign/adapters/__init__.py,sha256=bpDm5aBxMdq4OA_beYahoUb4zfNaq3KOG6KghQJruRc,827
|
|
21
21
|
realign/adapters/antigravity.py,sha256=geaYxAEswpgsVtERqsQ1OwvPFsy5tRkyjx2yQ-Uq9nM,5461
|
|
22
22
|
realign/adapters/base.py,sha256=2IdAZKGjg5gPB3YLf_8r3V4XAdbK7fHpj06GjjsYEFY,7409
|
|
@@ -34,17 +34,17 @@ realign/claude_hooks/user_prompt_submit_hook.py,sha256=WD-UavhBTueN2TPfnZrnPC7DF
|
|
|
34
34
|
realign/claude_hooks/user_prompt_submit_hook_installer.py,sha256=2xLF8yZcE7Iwib9gU-xCkA1NWxNH9Nc5CFKPYK7rtXw,5371
|
|
35
35
|
realign/commands/__init__.py,sha256=sx_ck55oxaoiF4N3LugG0ZXwonUDxeEZ5uHbBKCC7K8,89
|
|
36
36
|
realign/commands/add.py,sha256=njZgg3paUmOw-sb-sWkXr_eUaf5bD-hBEiRectaphPs,24332
|
|
37
|
-
realign/commands/auth.py,sha256=
|
|
37
|
+
realign/commands/auth.py,sha256=B1FQjaLUKYc4DsGqrpXZ3JPmsttUXhrtxxrKuOUmP4Q,10844
|
|
38
38
|
realign/commands/config.py,sha256=nYnu_h2pk7GODcrzrV04K51D-s7v06FlRXHJ0HJ-gvU,6732
|
|
39
39
|
realign/commands/context.py,sha256=pM2KfZHVkB-ou4nBhFvKSwnYliLBzwN3zerLyBAbhfE,7095
|
|
40
40
|
realign/commands/export_shares.py,sha256=g2SxlPEb7FPMarjTZcoZntviZo5JdqEe-M28vggc2-s,136601
|
|
41
|
-
realign/commands/import_shares.py,sha256=
|
|
41
|
+
realign/commands/import_shares.py,sha256=HiswLlYHqR0dR3wgB7Rs54_WownqahIs5IdyJOHuot8,25572
|
|
42
42
|
realign/commands/init.py,sha256=nhP1Qjl6Xo5R1ry_iTGVu3RwMxP-pYT5Z50NdzEMKrY,32756
|
|
43
43
|
realign/commands/restore.py,sha256=s2BxQZHxQw9r12NzRVsK20KlGafy5AIoSjWMo5PcnHY,11173
|
|
44
44
|
realign/commands/search.py,sha256=QJrC0hln9sCDFxXbpo0nPGMHXrud18qA5QfRyD0z6fQ,25926
|
|
45
45
|
realign/commands/upgrade.py,sha256=L3PLOUIN5qAQTbkfoVtSsIbbzEezA_xjjk9F1GMVfjw,12781
|
|
46
|
-
realign/commands/watcher.py,sha256=
|
|
47
|
-
realign/commands/worker.py,sha256=
|
|
46
|
+
realign/commands/watcher.py,sha256=rB3x2diC7scqghl7wLAeBNgMj4NLXUKbr0ou1_VVUIU,136292
|
|
47
|
+
realign/commands/worker.py,sha256=jTu7Pj60nTnn7SsH3oNCNnO6zl4TIFCJVNSC1OoQ_0o,23363
|
|
48
48
|
realign/dashboard/__init__.py,sha256=QZkHTsGityH8UkF8rmvA3xW7dMXNe0swEWr443qfgCM,128
|
|
49
49
|
realign/dashboard/app.py,sha256=jyW6mqmItTy253CPSqInxctkWzkrGEikdy-ikuShQ14,13299
|
|
50
50
|
realign/dashboard/tmux_manager.py,sha256=Vt_30WNtDg7c_9SEh8xdDtBLJ8kNq6bGSPh5r3VXpg0,26276
|
|
@@ -58,11 +58,11 @@ realign/dashboard/screens/share_import.py,sha256=hl2x0yGVycsoUI76AmdZTAV-br3Q619
|
|
|
58
58
|
realign/dashboard/styles/dashboard.tcss,sha256=ewonevBGLN-dfSsgxUk4VBCPchtxY4rx_vj1u6Ox2Fw,3454
|
|
59
59
|
realign/dashboard/widgets/__init__.py,sha256=3Pf2_K9obrertgv_psfxradgkI9RXlmjoXYQH7oBKm0,583
|
|
60
60
|
realign/dashboard/widgets/config_panel.py,sha256=JRv9Hgm5V-vqVptS7gQqnjg5MbKpt_FmdZV13D4E9A4,17894
|
|
61
|
-
realign/dashboard/widgets/events_table.py,sha256=
|
|
61
|
+
realign/dashboard/widgets/events_table.py,sha256=Ru4y5Xq04Y-3CN9Dwm-fHrHR37Aup9NFW_Yh-J1x-Jo,33247
|
|
62
62
|
realign/dashboard/widgets/header.py,sha256=0HHCFXX7F3C6HII-WDwOJwWkJrajmKPWmdoMWyOkn9E,1587
|
|
63
63
|
realign/dashboard/widgets/openable_table.py,sha256=GeJPDEYp0kRHShqvmPMzAePpYXRZHUNqcWNnxqsqxjA,1963
|
|
64
64
|
realign/dashboard/widgets/search_panel.py,sha256=ZNJDfwDSxUFnCeltYQYsQsPJ6t4HDeNWpENoTOoBdVM,8951
|
|
65
|
-
realign/dashboard/widgets/sessions_table.py,sha256=
|
|
65
|
+
realign/dashboard/widgets/sessions_table.py,sha256=GyaWzvt-elKx1iE2YR94CBNPyjqM8B7g0j7zsukiXi0,36517
|
|
66
66
|
realign/dashboard/widgets/terminal_panel.py,sha256=uoi3LjgYWyFE6Yr208KC5iKg0QxLcXpN6hCERlI6pBg,29069
|
|
67
67
|
realign/dashboard/widgets/watcher_panel.py,sha256=O_mdDacgc87xA-5KEfta53Ik_Xsk_B2OfwenMOTtGw8,19722
|
|
68
68
|
realign/dashboard/widgets/worker_panel.py,sha256=F_jKWABuCNmjQgeeuCr4KnFRKdY4CLTNcEXMYwsNaSk,18691
|
|
@@ -91,8 +91,8 @@ realign/triggers/next_turn_trigger.py,sha256=BpP0PWn4mU1MZd6mv89jWcjs8Jtv0zEWapW
|
|
|
91
91
|
realign/triggers/registry.py,sha256=cb-AVLbYB2pqwfWL3q1DQxLv4kOw7g7m-GshTdfFESc,3827
|
|
92
92
|
realign/triggers/turn_status.py,sha256=wAZEhXDAmDoX5F-ohWfSnZZ0eA6DAJ9svSPiSv_f6sg,6041
|
|
93
93
|
realign/triggers/turn_summary.py,sha256=f3hEUshgv9skJ9AbfWpoYs417lsv_HK2A_vpPjgryO4,4467
|
|
94
|
-
aline_ai-0.
|
|
95
|
-
aline_ai-0.
|
|
96
|
-
aline_ai-0.
|
|
97
|
-
aline_ai-0.
|
|
98
|
-
aline_ai-0.
|
|
94
|
+
aline_ai-0.6.0.dist-info/METADATA,sha256=oukhmryjZ2KFfmgLAIPBjpr8S4_8pxboUCOAgIWLx4Y,1597
|
|
95
|
+
aline_ai-0.6.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
96
|
+
aline_ai-0.6.0.dist-info/entry_points.txt,sha256=TvYELpMoWsUTcQdMV8tBHxCbEf_LbK4sESqK3r8PM6Y,78
|
|
97
|
+
aline_ai-0.6.0.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
|
|
98
|
+
aline_ai-0.6.0.dist-info/RECORD,,
|
realign/__init__.py
CHANGED
realign/cli.py
CHANGED
|
@@ -33,6 +33,26 @@ def main(
|
|
|
33
33
|
ctx.obj["dev"] = dev
|
|
34
34
|
|
|
35
35
|
if ctx.invoked_subcommand is None:
|
|
36
|
+
# Check login status before launching dashboard
|
|
37
|
+
from .auth import is_logged_in, get_current_user
|
|
38
|
+
|
|
39
|
+
if not is_logged_in():
|
|
40
|
+
console.print("[yellow]You need to login before using Aline.[/yellow]")
|
|
41
|
+
console.print("Starting login flow...\n")
|
|
42
|
+
|
|
43
|
+
# Run login command
|
|
44
|
+
exit_code = auth.login_command()
|
|
45
|
+
if exit_code != 0:
|
|
46
|
+
console.print("\n[red]Login failed. Please try again with 'aline login'.[/red]")
|
|
47
|
+
raise typer.Exit(code=1)
|
|
48
|
+
|
|
49
|
+
# Verify login succeeded
|
|
50
|
+
if not is_logged_in():
|
|
51
|
+
console.print("\n[red]Login verification failed. Please try again.[/red]")
|
|
52
|
+
raise typer.Exit(code=1)
|
|
53
|
+
|
|
54
|
+
console.print() # Add spacing before dashboard launch
|
|
55
|
+
|
|
36
56
|
# Check for updates before launching dashboard
|
|
37
57
|
from .commands.upgrade import check_and_prompt_update
|
|
38
58
|
|
|
@@ -79,6 +99,235 @@ def whoami_cli():
|
|
|
79
99
|
raise typer.Exit(code=exit_code)
|
|
80
100
|
|
|
81
101
|
|
|
102
|
+
@app.command(name="doctor")
|
|
103
|
+
def doctor_cli(
|
|
104
|
+
no_restart: bool = typer.Option(False, "--no-restart", help="Only clear cache, don't restart daemons"),
|
|
105
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed output"),
|
|
106
|
+
):
|
|
107
|
+
"""
|
|
108
|
+
Fix common issues after code updates.
|
|
109
|
+
|
|
110
|
+
This command:
|
|
111
|
+
- Clears Python bytecode cache (.pyc files)
|
|
112
|
+
- Updates Claude Code hooks (Stop, UserPromptSubmit, PermissionRequest)
|
|
113
|
+
- Updates skills to latest version
|
|
114
|
+
- Restarts the watcher daemon (if running)
|
|
115
|
+
- Restarts the worker daemon (if running)
|
|
116
|
+
|
|
117
|
+
Run this after pulling new code to ensure everything uses the latest version.
|
|
118
|
+
"""
|
|
119
|
+
import shutil
|
|
120
|
+
import subprocess
|
|
121
|
+
import signal
|
|
122
|
+
import time
|
|
123
|
+
|
|
124
|
+
# Find the project root (where src/realign is)
|
|
125
|
+
project_root = Path(__file__).parent.parent.parent
|
|
126
|
+
|
|
127
|
+
# 1. Clear Python cache
|
|
128
|
+
console.print("[bold]1. Clearing Python cache...[/bold]")
|
|
129
|
+
pyc_count = 0
|
|
130
|
+
pycache_count = 0
|
|
131
|
+
|
|
132
|
+
for pyc_file in project_root.rglob("*.pyc"):
|
|
133
|
+
try:
|
|
134
|
+
pyc_file.unlink()
|
|
135
|
+
pyc_count += 1
|
|
136
|
+
if verbose:
|
|
137
|
+
console.print(f" [dim]Removed: {pyc_file}[/dim]")
|
|
138
|
+
except Exception as e:
|
|
139
|
+
if verbose:
|
|
140
|
+
console.print(f" [yellow]Failed to remove {pyc_file}: {e}[/yellow]")
|
|
141
|
+
|
|
142
|
+
for pycache_dir in project_root.rglob("__pycache__"):
|
|
143
|
+
if pycache_dir.is_dir():
|
|
144
|
+
try:
|
|
145
|
+
shutil.rmtree(pycache_dir)
|
|
146
|
+
pycache_count += 1
|
|
147
|
+
if verbose:
|
|
148
|
+
console.print(f" [dim]Removed: {pycache_dir}[/dim]")
|
|
149
|
+
except Exception as e:
|
|
150
|
+
if verbose:
|
|
151
|
+
console.print(f" [yellow]Failed to remove {pycache_dir}: {e}[/yellow]")
|
|
152
|
+
|
|
153
|
+
console.print(f" [green]✓[/green] Cleared {pyc_count} .pyc files, {pycache_count} __pycache__ directories")
|
|
154
|
+
|
|
155
|
+
# 2. Update Claude Code hooks
|
|
156
|
+
console.print("\n[bold]2. Updating Claude Code hooks...[/bold]")
|
|
157
|
+
hooks_updated = []
|
|
158
|
+
hooks_failed = []
|
|
159
|
+
|
|
160
|
+
# Stop hook
|
|
161
|
+
try:
|
|
162
|
+
from .claude_hooks.stop_hook_installer import install_stop_hook, get_settings_path
|
|
163
|
+
if install_stop_hook(get_settings_path(), quiet=True, force=True):
|
|
164
|
+
hooks_updated.append("Stop")
|
|
165
|
+
if verbose:
|
|
166
|
+
console.print(" [dim]Stop hook updated[/dim]")
|
|
167
|
+
else:
|
|
168
|
+
hooks_failed.append("Stop")
|
|
169
|
+
except Exception as e:
|
|
170
|
+
hooks_failed.append("Stop")
|
|
171
|
+
if verbose:
|
|
172
|
+
console.print(f" [yellow]Stop hook failed: {e}[/yellow]")
|
|
173
|
+
|
|
174
|
+
# UserPromptSubmit hook
|
|
175
|
+
try:
|
|
176
|
+
from .claude_hooks.user_prompt_submit_hook_installer import install_user_prompt_submit_hook, get_settings_path as get_submit_settings_path
|
|
177
|
+
if install_user_prompt_submit_hook(get_submit_settings_path(), quiet=True, force=True):
|
|
178
|
+
hooks_updated.append("UserPromptSubmit")
|
|
179
|
+
if verbose:
|
|
180
|
+
console.print(" [dim]UserPromptSubmit hook updated[/dim]")
|
|
181
|
+
else:
|
|
182
|
+
hooks_failed.append("UserPromptSubmit")
|
|
183
|
+
except Exception as e:
|
|
184
|
+
hooks_failed.append("UserPromptSubmit")
|
|
185
|
+
if verbose:
|
|
186
|
+
console.print(f" [yellow]UserPromptSubmit hook failed: {e}[/yellow]")
|
|
187
|
+
|
|
188
|
+
# PermissionRequest hook
|
|
189
|
+
try:
|
|
190
|
+
from .claude_hooks.permission_request_hook_installer import install_permission_request_hook, get_settings_path as get_permission_settings_path
|
|
191
|
+
if install_permission_request_hook(get_permission_settings_path(), quiet=True, force=True):
|
|
192
|
+
hooks_updated.append("PermissionRequest")
|
|
193
|
+
if verbose:
|
|
194
|
+
console.print(" [dim]PermissionRequest hook updated[/dim]")
|
|
195
|
+
else:
|
|
196
|
+
hooks_failed.append("PermissionRequest")
|
|
197
|
+
except Exception as e:
|
|
198
|
+
hooks_failed.append("PermissionRequest")
|
|
199
|
+
if verbose:
|
|
200
|
+
console.print(f" [yellow]PermissionRequest hook failed: {e}[/yellow]")
|
|
201
|
+
|
|
202
|
+
if hooks_updated:
|
|
203
|
+
console.print(f" [green]✓[/green] Updated hooks: {', '.join(hooks_updated)}")
|
|
204
|
+
if hooks_failed:
|
|
205
|
+
console.print(f" [yellow]![/yellow] Failed hooks: {', '.join(hooks_failed)}")
|
|
206
|
+
|
|
207
|
+
# 3. Update skills
|
|
208
|
+
console.print("\n[bold]3. Updating skills...[/bold]")
|
|
209
|
+
try:
|
|
210
|
+
from .commands.add import add_skills_command
|
|
211
|
+
# Capture output by redirecting - use force=True to update
|
|
212
|
+
import io
|
|
213
|
+
import contextlib
|
|
214
|
+
|
|
215
|
+
stdout_capture = io.StringIO()
|
|
216
|
+
with contextlib.redirect_stdout(stdout_capture):
|
|
217
|
+
add_skills_command(force=True)
|
|
218
|
+
|
|
219
|
+
output = stdout_capture.getvalue()
|
|
220
|
+
# Count updated skills from output
|
|
221
|
+
updated_count = output.count("✓")
|
|
222
|
+
if updated_count > 0:
|
|
223
|
+
console.print(f" [green]✓[/green] Updated {updated_count} skill(s)")
|
|
224
|
+
else:
|
|
225
|
+
console.print(" [green]✓[/green] Skills are up to date")
|
|
226
|
+
if verbose and output.strip():
|
|
227
|
+
for line in output.strip().split("\n"):
|
|
228
|
+
console.print(f" [dim]{line}[/dim]")
|
|
229
|
+
except Exception as e:
|
|
230
|
+
console.print(f" [yellow]![/yellow] Failed to update skills: {e}")
|
|
231
|
+
|
|
232
|
+
if no_restart:
|
|
233
|
+
console.print("\n[dim]Skipping daemon restart (--no-restart)[/dim]")
|
|
234
|
+
console.print("\n[green]Done![/green] Aline is ready with the latest code.")
|
|
235
|
+
raise typer.Exit(code=0)
|
|
236
|
+
|
|
237
|
+
# 4. Restart watcher daemon
|
|
238
|
+
console.print("\n[bold]4. Checking watcher daemon...[/bold]")
|
|
239
|
+
pid_file = Path.home() / ".aline" / ".logs" / "watcher.pid"
|
|
240
|
+
watcher_was_running = False
|
|
241
|
+
|
|
242
|
+
if pid_file.exists():
|
|
243
|
+
try:
|
|
244
|
+
pid = int(pid_file.read_text().strip())
|
|
245
|
+
# Check if process is running
|
|
246
|
+
try:
|
|
247
|
+
import os
|
|
248
|
+
os.kill(pid, 0) # Signal 0 just checks if process exists
|
|
249
|
+
watcher_was_running = True
|
|
250
|
+
console.print(f" [dim]Found watcher daemon (PID {pid}), stopping...[/dim]")
|
|
251
|
+
os.kill(pid, signal.SIGTERM)
|
|
252
|
+
time.sleep(1)
|
|
253
|
+
# Force kill if still running
|
|
254
|
+
try:
|
|
255
|
+
os.kill(pid, 0)
|
|
256
|
+
os.kill(pid, signal.SIGKILL)
|
|
257
|
+
time.sleep(0.5)
|
|
258
|
+
except ProcessLookupError:
|
|
259
|
+
pass
|
|
260
|
+
except ProcessLookupError:
|
|
261
|
+
console.print(" [dim]Watcher daemon not running (stale PID file)[/dim]")
|
|
262
|
+
except Exception as e:
|
|
263
|
+
if verbose:
|
|
264
|
+
console.print(f" [yellow]Error checking watcher: {e}[/yellow]")
|
|
265
|
+
|
|
266
|
+
if watcher_was_running:
|
|
267
|
+
console.print(" [dim]Starting watcher daemon...[/dim]")
|
|
268
|
+
try:
|
|
269
|
+
subprocess.Popen(
|
|
270
|
+
["python", "-m", "src.realign.watcher_daemon"],
|
|
271
|
+
stdout=subprocess.DEVNULL,
|
|
272
|
+
stderr=subprocess.DEVNULL,
|
|
273
|
+
start_new_session=True,
|
|
274
|
+
cwd=str(project_root),
|
|
275
|
+
)
|
|
276
|
+
time.sleep(2)
|
|
277
|
+
console.print(" [green]✓[/green] Watcher daemon restarted")
|
|
278
|
+
except Exception as e:
|
|
279
|
+
console.print(f" [red]✗[/red] Failed to restart watcher: {e}")
|
|
280
|
+
else:
|
|
281
|
+
console.print(" [dim]Watcher daemon was not running[/dim]")
|
|
282
|
+
|
|
283
|
+
# 5. Restart worker daemon
|
|
284
|
+
console.print("\n[bold]5. Checking worker daemon...[/bold]")
|
|
285
|
+
worker_pid_file = Path.home() / ".aline" / ".logs" / "worker.pid"
|
|
286
|
+
worker_was_running = False
|
|
287
|
+
|
|
288
|
+
if worker_pid_file.exists():
|
|
289
|
+
try:
|
|
290
|
+
pid = int(worker_pid_file.read_text().strip())
|
|
291
|
+
try:
|
|
292
|
+
import os
|
|
293
|
+
os.kill(pid, 0)
|
|
294
|
+
worker_was_running = True
|
|
295
|
+
console.print(f" [dim]Found worker daemon (PID {pid}), stopping...[/dim]")
|
|
296
|
+
os.kill(pid, signal.SIGTERM)
|
|
297
|
+
time.sleep(1)
|
|
298
|
+
try:
|
|
299
|
+
os.kill(pid, 0)
|
|
300
|
+
os.kill(pid, signal.SIGKILL)
|
|
301
|
+
time.sleep(0.5)
|
|
302
|
+
except ProcessLookupError:
|
|
303
|
+
pass
|
|
304
|
+
except ProcessLookupError:
|
|
305
|
+
console.print(" [dim]Worker daemon not running (stale PID file)[/dim]")
|
|
306
|
+
except Exception as e:
|
|
307
|
+
if verbose:
|
|
308
|
+
console.print(f" [yellow]Error checking worker: {e}[/yellow]")
|
|
309
|
+
|
|
310
|
+
if worker_was_running:
|
|
311
|
+
console.print(" [dim]Starting worker daemon...[/dim]")
|
|
312
|
+
try:
|
|
313
|
+
subprocess.Popen(
|
|
314
|
+
["python", "-m", "src.realign.worker_daemon"],
|
|
315
|
+
stdout=subprocess.DEVNULL,
|
|
316
|
+
stderr=subprocess.DEVNULL,
|
|
317
|
+
start_new_session=True,
|
|
318
|
+
cwd=str(project_root),
|
|
319
|
+
)
|
|
320
|
+
time.sleep(2)
|
|
321
|
+
console.print(" [green]✓[/green] Worker daemon restarted")
|
|
322
|
+
except Exception as e:
|
|
323
|
+
console.print(f" [red]✗[/red] Failed to restart worker: {e}")
|
|
324
|
+
else:
|
|
325
|
+
console.print(" [dim]Worker daemon was not running[/dim]")
|
|
326
|
+
|
|
327
|
+
console.print("\n[green]Done![/green] Aline is ready with the latest code.")
|
|
328
|
+
raise typer.Exit(code=0)
|
|
329
|
+
|
|
330
|
+
|
|
82
331
|
@app.command(name="search")
|
|
83
332
|
def search_cli(
|
|
84
333
|
query: str = typer.Argument(..., help="Search query (keywords or regex pattern)"),
|
realign/commands/auth.py
CHANGED
|
@@ -176,6 +176,8 @@ def logout_command() -> int:
|
|
|
176
176
|
"""
|
|
177
177
|
Logout from Aline and clear local credentials.
|
|
178
178
|
|
|
179
|
+
Also stops watcher and worker daemons since they require authentication.
|
|
180
|
+
|
|
179
181
|
Returns:
|
|
180
182
|
0 on success, 1 on error
|
|
181
183
|
"""
|
|
@@ -190,6 +192,14 @@ def logout_command() -> int:
|
|
|
190
192
|
|
|
191
193
|
email = credentials.email
|
|
192
194
|
|
|
195
|
+
# Stop watcher and worker daemons before logout
|
|
196
|
+
if console:
|
|
197
|
+
console.print("[dim]Stopping daemons...[/dim]")
|
|
198
|
+
else:
|
|
199
|
+
print("Stopping daemons...")
|
|
200
|
+
|
|
201
|
+
_stop_daemons()
|
|
202
|
+
|
|
193
203
|
if not clear_credentials():
|
|
194
204
|
if console:
|
|
195
205
|
console.print("[red]Error: Failed to clear credentials[/red]")
|
|
@@ -208,6 +218,97 @@ def logout_command() -> int:
|
|
|
208
218
|
return 0
|
|
209
219
|
|
|
210
220
|
|
|
221
|
+
def _stop_daemons() -> None:
|
|
222
|
+
"""Stop watcher and worker daemons."""
|
|
223
|
+
import os
|
|
224
|
+
import signal
|
|
225
|
+
import time
|
|
226
|
+
from pathlib import Path
|
|
227
|
+
|
|
228
|
+
def stop_daemon(name: str, pid_file: Path) -> None:
|
|
229
|
+
"""Stop a daemon by PID file, wait for it to terminate."""
|
|
230
|
+
if not pid_file.exists():
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
pid = int(pid_file.read_text().strip())
|
|
235
|
+
except Exception:
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
# Check if process is running
|
|
239
|
+
try:
|
|
240
|
+
os.kill(pid, 0)
|
|
241
|
+
except ProcessLookupError:
|
|
242
|
+
# Process already gone, clean up PID file
|
|
243
|
+
try:
|
|
244
|
+
pid_file.unlink(missing_ok=True)
|
|
245
|
+
except Exception:
|
|
246
|
+
pass
|
|
247
|
+
return
|
|
248
|
+
except PermissionError:
|
|
249
|
+
pass # Can't check, try to stop anyway
|
|
250
|
+
|
|
251
|
+
# Send SIGTERM
|
|
252
|
+
try:
|
|
253
|
+
os.kill(pid, signal.SIGTERM)
|
|
254
|
+
if console:
|
|
255
|
+
console.print(f" [dim]Stopping {name} daemon (PID {pid})...[/dim]")
|
|
256
|
+
logger.info(f"Sent SIGTERM to {name} daemon (PID {pid})")
|
|
257
|
+
except ProcessLookupError:
|
|
258
|
+
try:
|
|
259
|
+
pid_file.unlink(missing_ok=True)
|
|
260
|
+
except Exception:
|
|
261
|
+
pass
|
|
262
|
+
return
|
|
263
|
+
except Exception as e:
|
|
264
|
+
logger.debug(f"Error sending SIGTERM to {name}: {e}")
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
# Wait for process to terminate (up to 5 seconds)
|
|
268
|
+
for _ in range(50): # 50 * 0.1s = 5 seconds
|
|
269
|
+
time.sleep(0.1)
|
|
270
|
+
try:
|
|
271
|
+
os.kill(pid, 0)
|
|
272
|
+
except ProcessLookupError:
|
|
273
|
+
# Process terminated
|
|
274
|
+
if console:
|
|
275
|
+
console.print(f" [dim]{name.capitalize()} daemon stopped[/dim]")
|
|
276
|
+
logger.info(f"{name.capitalize()} daemon (PID {pid}) stopped")
|
|
277
|
+
try:
|
|
278
|
+
pid_file.unlink(missing_ok=True)
|
|
279
|
+
except Exception:
|
|
280
|
+
pass
|
|
281
|
+
return
|
|
282
|
+
except PermissionError:
|
|
283
|
+
break # Can't check anymore
|
|
284
|
+
|
|
285
|
+
# Process still running, try SIGKILL
|
|
286
|
+
try:
|
|
287
|
+
os.kill(pid, signal.SIGKILL)
|
|
288
|
+
time.sleep(0.5)
|
|
289
|
+
if console:
|
|
290
|
+
console.print(f" [dim]{name.capitalize()} daemon force killed[/dim]")
|
|
291
|
+
logger.info(f"{name.capitalize()} daemon (PID {pid}) force killed")
|
|
292
|
+
except ProcessLookupError:
|
|
293
|
+
pass
|
|
294
|
+
except Exception as e:
|
|
295
|
+
logger.debug(f"Error sending SIGKILL to {name}: {e}")
|
|
296
|
+
|
|
297
|
+
# Clean up PID file
|
|
298
|
+
try:
|
|
299
|
+
pid_file.unlink(missing_ok=True)
|
|
300
|
+
except Exception:
|
|
301
|
+
pass
|
|
302
|
+
|
|
303
|
+
# Stop watcher daemon
|
|
304
|
+
watcher_pid_file = Path.home() / ".aline" / ".logs" / "watcher.pid"
|
|
305
|
+
stop_daemon("watcher", watcher_pid_file)
|
|
306
|
+
|
|
307
|
+
# Stop worker daemon
|
|
308
|
+
worker_pid_file = Path.home() / ".aline" / ".logs" / "worker.pid"
|
|
309
|
+
stop_daemon("worker", worker_pid_file)
|
|
310
|
+
|
|
311
|
+
|
|
211
312
|
def whoami_command() -> int:
|
|
212
313
|
"""
|
|
213
314
|
Display current login status.
|
|
@@ -239,6 +239,9 @@ def import_v2_data(
|
|
|
239
239
|
|
|
240
240
|
logger.info("Starting v2.0 data import")
|
|
241
241
|
|
|
242
|
+
# Load config to get current user's UID for shared_by
|
|
243
|
+
config = ReAlignConfig.load()
|
|
244
|
+
|
|
242
245
|
# 1. Create Event
|
|
243
246
|
event_data = data.get("event", {})
|
|
244
247
|
event_id = event_data.get("event_id")
|
|
@@ -366,6 +369,9 @@ def import_session_with_turns(
|
|
|
366
369
|
Returns:
|
|
367
370
|
Dict with counts: {'sessions': int, 'turns': int, 'skipped': int}
|
|
368
371
|
"""
|
|
372
|
+
# Load config to get current user's UID for shared_by
|
|
373
|
+
config = ReAlignConfig.load()
|
|
374
|
+
|
|
369
375
|
session_id = session_data.get("session_id")
|
|
370
376
|
imported_sessions = 0
|
|
371
377
|
imported_turns = 0
|
realign/commands/watcher.py
CHANGED
|
@@ -658,6 +658,14 @@ def watcher_start_command() -> int:
|
|
|
658
658
|
int: Exit code (0 = success, 1 = error)
|
|
659
659
|
"""
|
|
660
660
|
try:
|
|
661
|
+
# Check login status first
|
|
662
|
+
from ..auth import is_logged_in
|
|
663
|
+
|
|
664
|
+
if not is_logged_in():
|
|
665
|
+
console.print("[red]✗ Not logged in. Watcher requires authentication.[/red]")
|
|
666
|
+
console.print("[dim]Run 'aline login' first.[/dim]")
|
|
667
|
+
return 1
|
|
668
|
+
|
|
661
669
|
# Check if already running
|
|
662
670
|
is_running, pid, mode = detect_watcher_process()
|
|
663
671
|
|
realign/commands/worker.py
CHANGED
|
@@ -461,6 +461,14 @@ def worker_repair_command(*, force: bool = False) -> int:
|
|
|
461
461
|
|
|
462
462
|
def worker_start_command() -> int:
|
|
463
463
|
try:
|
|
464
|
+
# Check login status first
|
|
465
|
+
from ..auth import is_logged_in
|
|
466
|
+
|
|
467
|
+
if not is_logged_in():
|
|
468
|
+
console.print("[red]✗ Not logged in. Worker requires authentication.[/red]")
|
|
469
|
+
console.print("[dim]Run 'aline login' first.[/dim]")
|
|
470
|
+
return 1
|
|
471
|
+
|
|
464
472
|
is_running, pid, mode = detect_worker_process()
|
|
465
473
|
if is_running:
|
|
466
474
|
console.print(f"[yellow]Worker is already running (PID: {pid}, mode: {mode})[/yellow]")
|
|
@@ -232,6 +232,8 @@ class EventsTable(Container):
|
|
|
232
232
|
table.add_column("Share", key="share", width=12)
|
|
233
233
|
table.add_column("Type", key="type", width=8)
|
|
234
234
|
table.add_column("Sessions", key="sessions", width=8)
|
|
235
|
+
table.add_column("Created By", key="created_by", width=10)
|
|
236
|
+
table.add_column("Shared By", key="shared_by", width=10)
|
|
235
237
|
table.add_column("Event ID", key="event_id", width=12)
|
|
236
238
|
table.add_column("Created", key="created", width=10)
|
|
237
239
|
table.cursor_type = "row"
|
|
@@ -352,6 +354,12 @@ class EventsTable(Container):
|
|
|
352
354
|
table.update_cell(
|
|
353
355
|
eid, "sessions", self._format_cell(str(event["sessions"]), eid)
|
|
354
356
|
)
|
|
357
|
+
table.update_cell(
|
|
358
|
+
eid, "created_by", self._format_cell(event.get("created_by", "-"), eid)
|
|
359
|
+
)
|
|
360
|
+
table.update_cell(
|
|
361
|
+
eid, "shared_by", self._format_cell(event.get("shared_by", "-"), eid)
|
|
362
|
+
)
|
|
355
363
|
table.update_cell(
|
|
356
364
|
eid, "event_id", self._format_cell(event["short_id"], eid)
|
|
357
365
|
)
|
|
@@ -736,6 +744,8 @@ class EventsTable(Container):
|
|
|
736
744
|
e.event_type,
|
|
737
745
|
e.created_at,
|
|
738
746
|
e.share_url,
|
|
747
|
+
e.created_by,
|
|
748
|
+
e.shared_by,
|
|
739
749
|
(SELECT COUNT(*) FROM event_sessions WHERE event_id = e.id) AS session_count
|
|
740
750
|
FROM events e
|
|
741
751
|
ORDER BY e.created_at DESC
|
|
@@ -743,8 +753,9 @@ class EventsTable(Container):
|
|
|
743
753
|
""",
|
|
744
754
|
(int(rows_per_page), int(offset)),
|
|
745
755
|
).fetchall()
|
|
746
|
-
|
|
756
|
+
has_new_columns = True
|
|
747
757
|
except Exception:
|
|
758
|
+
# Fallback for older schema without created_by/shared_by
|
|
748
759
|
rows = conn.execute(
|
|
749
760
|
"""
|
|
750
761
|
SELECT
|
|
@@ -759,18 +770,22 @@ class EventsTable(Container):
|
|
|
759
770
|
""",
|
|
760
771
|
(int(rows_per_page), int(offset)),
|
|
761
772
|
).fetchall()
|
|
762
|
-
|
|
773
|
+
has_new_columns = False
|
|
763
774
|
|
|
764
775
|
for i, row in enumerate(rows):
|
|
765
776
|
event_id = row[0]
|
|
766
777
|
title = row[1] or "(no title)"
|
|
767
778
|
event_type = row[2] or "unknown"
|
|
768
779
|
created_at = row[3]
|
|
769
|
-
if
|
|
780
|
+
if has_new_columns:
|
|
770
781
|
share_url = row[4]
|
|
771
|
-
|
|
782
|
+
created_by = row[5]
|
|
783
|
+
shared_by = row[6]
|
|
784
|
+
session_count = row[7]
|
|
772
785
|
else:
|
|
773
786
|
share_url = None
|
|
787
|
+
created_by = None
|
|
788
|
+
shared_by = None
|
|
774
789
|
session_count = row[4]
|
|
775
790
|
|
|
776
791
|
# Format event type
|
|
@@ -789,6 +804,26 @@ class EventsTable(Container):
|
|
|
789
804
|
except Exception:
|
|
790
805
|
created_str = created_at
|
|
791
806
|
|
|
807
|
+
# Look up user names from users table
|
|
808
|
+
created_by_display = "-"
|
|
809
|
+
shared_by_display = "-"
|
|
810
|
+
if created_by:
|
|
811
|
+
try:
|
|
812
|
+
user_row = conn.execute(
|
|
813
|
+
"SELECT user_name FROM users WHERE uid = ?", (created_by,)
|
|
814
|
+
).fetchone()
|
|
815
|
+
created_by_display = user_row[0] if user_row and user_row[0] else created_by[:8] + "..."
|
|
816
|
+
except Exception:
|
|
817
|
+
created_by_display = created_by[:8] + "..." if len(created_by) > 8 else created_by
|
|
818
|
+
if shared_by:
|
|
819
|
+
try:
|
|
820
|
+
user_row = conn.execute(
|
|
821
|
+
"SELECT user_name FROM users WHERE uid = ?", (shared_by,)
|
|
822
|
+
).fetchone()
|
|
823
|
+
shared_by_display = user_row[0] if user_row and user_row[0] else shared_by[:8] + "..."
|
|
824
|
+
except Exception:
|
|
825
|
+
shared_by_display = shared_by[:8] + "..." if len(shared_by) > 8 else shared_by
|
|
826
|
+
|
|
792
827
|
events.append(
|
|
793
828
|
{
|
|
794
829
|
"index": offset + i + 1,
|
|
@@ -799,6 +834,8 @@ class EventsTable(Container):
|
|
|
799
834
|
"sessions": session_count,
|
|
800
835
|
"share_url": share_url,
|
|
801
836
|
"share_id": self._extract_share_id(share_url),
|
|
837
|
+
"created_by": created_by_display,
|
|
838
|
+
"shared_by": shared_by_display,
|
|
802
839
|
"created": created_str,
|
|
803
840
|
}
|
|
804
841
|
)
|
|
@@ -849,7 +886,7 @@ class EventsTable(Container):
|
|
|
849
886
|
if self.wrap_mode and event.get("share_url"):
|
|
850
887
|
share_val = event.get("share_url")
|
|
851
888
|
|
|
852
|
-
# Column order: ✓, #, Title, Share, Type, Sessions, Event ID, Created
|
|
889
|
+
# Column order: ✓, #, Title, Share, Type, Sessions, Created By, Shared By, Event ID, Created
|
|
853
890
|
table.add_row(
|
|
854
891
|
self._checkbox_cell(eid),
|
|
855
892
|
self._format_cell(str(event["index"]), eid),
|
|
@@ -857,6 +894,8 @@ class EventsTable(Container):
|
|
|
857
894
|
self._format_cell(share_val, eid),
|
|
858
895
|
self._format_cell(event["type"], eid),
|
|
859
896
|
self._format_cell(str(event["sessions"]), eid),
|
|
897
|
+
self._format_cell(event.get("created_by", "-"), eid),
|
|
898
|
+
self._format_cell(event.get("shared_by", "-"), eid),
|
|
860
899
|
self._format_cell(event["short_id"], eid),
|
|
861
900
|
self._format_cell(event["created"], eid),
|
|
862
901
|
key=eid,
|
|
@@ -223,6 +223,8 @@ class SessionsTable(Container):
|
|
|
223
223
|
table.add_column("Project", key="project", width=15)
|
|
224
224
|
table.add_column("Source", key="source", width=10)
|
|
225
225
|
table.add_column("Turns", key="turns", width=6)
|
|
226
|
+
table.add_column("Created By", key="created_by", width=10)
|
|
227
|
+
table.add_column("Shared By", key="shared_by", width=10)
|
|
226
228
|
table.add_column("Session ID", key="session_id", width=20)
|
|
227
229
|
table.add_column("Last Activity", key="last_activity", width=12)
|
|
228
230
|
table.cursor_type = "row"
|
|
@@ -361,6 +363,12 @@ class SessionsTable(Container):
|
|
|
361
363
|
table.update_cell(
|
|
362
364
|
sid, "turns", self._format_cell(str(session["turns"]), sid)
|
|
363
365
|
)
|
|
366
|
+
table.update_cell(
|
|
367
|
+
sid, "created_by", self._format_cell(session.get("created_by", "-"), sid)
|
|
368
|
+
)
|
|
369
|
+
table.update_cell(
|
|
370
|
+
sid, "shared_by", self._format_cell(session.get("shared_by", "-"), sid)
|
|
371
|
+
)
|
|
364
372
|
table.update_cell(
|
|
365
373
|
sid, "session_id", self._format_cell(session["short_id"], sid)
|
|
366
374
|
)
|
|
@@ -805,21 +813,43 @@ class SessionsTable(Container):
|
|
|
805
813
|
# Get paginated sessions
|
|
806
814
|
# Use cached total_turns instead of subquery for performance
|
|
807
815
|
offset = (int(page) - 1) * int(rows_per_page)
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
816
|
+
try:
|
|
817
|
+
rows = conn.execute(
|
|
818
|
+
"""
|
|
819
|
+
SELECT
|
|
820
|
+
s.id,
|
|
821
|
+
s.session_type,
|
|
822
|
+
s.workspace_path,
|
|
823
|
+
s.session_title,
|
|
824
|
+
s.last_activity_at,
|
|
825
|
+
s.total_turns,
|
|
826
|
+
s.created_by,
|
|
827
|
+
s.shared_by
|
|
828
|
+
FROM sessions s
|
|
829
|
+
ORDER BY s.last_activity_at DESC
|
|
830
|
+
LIMIT ? OFFSET ?
|
|
831
|
+
""",
|
|
832
|
+
(int(rows_per_page), int(offset)),
|
|
833
|
+
).fetchall()
|
|
834
|
+
has_new_columns = True
|
|
835
|
+
except Exception:
|
|
836
|
+
# Fallback for older schema without created_by/shared_by
|
|
837
|
+
rows = conn.execute(
|
|
838
|
+
"""
|
|
839
|
+
SELECT
|
|
840
|
+
s.id,
|
|
841
|
+
s.session_type,
|
|
842
|
+
s.workspace_path,
|
|
843
|
+
s.session_title,
|
|
844
|
+
s.last_activity_at,
|
|
845
|
+
s.total_turns
|
|
846
|
+
FROM sessions s
|
|
847
|
+
ORDER BY s.last_activity_at DESC
|
|
848
|
+
LIMIT ? OFFSET ?
|
|
849
|
+
""",
|
|
850
|
+
(int(rows_per_page), int(offset)),
|
|
851
|
+
).fetchall()
|
|
852
|
+
has_new_columns = False
|
|
823
853
|
|
|
824
854
|
for i, row in enumerate(rows):
|
|
825
855
|
session_id = row[0]
|
|
@@ -828,6 +858,12 @@ class SessionsTable(Container):
|
|
|
828
858
|
title = row[3] or "(no title)"
|
|
829
859
|
last_activity = row[4]
|
|
830
860
|
turn_count = row[5]
|
|
861
|
+
if has_new_columns:
|
|
862
|
+
created_by = row[6]
|
|
863
|
+
shared_by = row[7]
|
|
864
|
+
else:
|
|
865
|
+
created_by = None
|
|
866
|
+
shared_by = None
|
|
831
867
|
|
|
832
868
|
source_map = {
|
|
833
869
|
"claude": "Claude",
|
|
@@ -846,6 +882,26 @@ class SessionsTable(Container):
|
|
|
846
882
|
except Exception:
|
|
847
883
|
activity_str = last_activity
|
|
848
884
|
|
|
885
|
+
# Look up user names from users table
|
|
886
|
+
created_by_display = "-"
|
|
887
|
+
shared_by_display = "-"
|
|
888
|
+
if created_by:
|
|
889
|
+
try:
|
|
890
|
+
user_row = conn.execute(
|
|
891
|
+
"SELECT user_name FROM users WHERE uid = ?", (created_by,)
|
|
892
|
+
).fetchone()
|
|
893
|
+
created_by_display = user_row[0] if user_row and user_row[0] else created_by[:8] + "..."
|
|
894
|
+
except Exception:
|
|
895
|
+
created_by_display = created_by[:8] + "..." if len(created_by) > 8 else created_by
|
|
896
|
+
if shared_by:
|
|
897
|
+
try:
|
|
898
|
+
user_row = conn.execute(
|
|
899
|
+
"SELECT user_name FROM users WHERE uid = ?", (shared_by,)
|
|
900
|
+
).fetchone()
|
|
901
|
+
shared_by_display = user_row[0] if user_row and user_row[0] else shared_by[:8] + "..."
|
|
902
|
+
except Exception:
|
|
903
|
+
shared_by_display = shared_by[:8] + "..." if len(shared_by) > 8 else shared_by
|
|
904
|
+
|
|
849
905
|
sessions.append(
|
|
850
906
|
{
|
|
851
907
|
"index": offset + i + 1,
|
|
@@ -855,6 +911,8 @@ class SessionsTable(Container):
|
|
|
855
911
|
"project": project,
|
|
856
912
|
"turns": turn_count,
|
|
857
913
|
"title": title,
|
|
914
|
+
"created_by": created_by_display,
|
|
915
|
+
"shared_by": shared_by_display,
|
|
858
916
|
"last_activity": activity_str,
|
|
859
917
|
}
|
|
860
918
|
)
|
|
@@ -905,7 +963,7 @@ class SessionsTable(Container):
|
|
|
905
963
|
if self.wrap_mode:
|
|
906
964
|
display_id = sid
|
|
907
965
|
|
|
908
|
-
# Column order: ✓, #, Title, Project, Source, Turns, Session ID, Last Activity
|
|
966
|
+
# Column order: ✓, #, Title, Project, Source, Turns, Created By, Shared By, Session ID, Last Activity
|
|
909
967
|
table.add_row(
|
|
910
968
|
self._checkbox_cell(sid),
|
|
911
969
|
self._format_cell(str(session["index"]), sid),
|
|
@@ -913,6 +971,8 @@ class SessionsTable(Container):
|
|
|
913
971
|
self._format_cell(session["project"], sid),
|
|
914
972
|
self._format_cell(session["source"], sid),
|
|
915
973
|
self._format_cell(str(session["turns"]), sid),
|
|
974
|
+
self._format_cell(session.get("created_by", "-"), sid),
|
|
975
|
+
self._format_cell(session.get("shared_by", "-"), sid),
|
|
916
976
|
self._format_cell(display_id, sid),
|
|
917
977
|
self._format_cell(session["last_activity"], sid),
|
|
918
978
|
key=sid,
|
realign/watcher_daemon.py
CHANGED
|
@@ -48,6 +48,17 @@ async def run_daemon():
|
|
|
48
48
|
"""Run the watcher daemon."""
|
|
49
49
|
watcher = None
|
|
50
50
|
|
|
51
|
+
# Check login status before starting
|
|
52
|
+
try:
|
|
53
|
+
from .auth import is_logged_in
|
|
54
|
+
except ImportError:
|
|
55
|
+
from realign.auth import is_logged_in
|
|
56
|
+
|
|
57
|
+
if not is_logged_in():
|
|
58
|
+
logger.error("Not logged in. Watcher daemon requires authentication.")
|
|
59
|
+
print("[Watcher Daemon] Error: Not logged in. Run 'aline login' first.", file=sys.stderr)
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
51
62
|
# Shutdown handler
|
|
52
63
|
def handle_shutdown(signum, frame):
|
|
53
64
|
"""Handle shutdown signals gracefully."""
|
realign/worker_daemon.py
CHANGED
|
@@ -49,6 +49,17 @@ async def run_daemon() -> None:
|
|
|
49
49
|
worker = None
|
|
50
50
|
db = None
|
|
51
51
|
|
|
52
|
+
# Check login status before starting
|
|
53
|
+
try:
|
|
54
|
+
from .auth import is_logged_in
|
|
55
|
+
except ImportError:
|
|
56
|
+
from realign.auth import is_logged_in
|
|
57
|
+
|
|
58
|
+
if not is_logged_in():
|
|
59
|
+
logger.error("Not logged in. Worker daemon requires authentication.")
|
|
60
|
+
print("[Worker Daemon] Error: Not logged in. Run 'aline login' first.", file=sys.stderr)
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
52
63
|
def handle_shutdown(signum, frame):
|
|
53
64
|
logger.info(f"Received signal {signum}, shutting down...")
|
|
54
65
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|