onako 0.4.1__py3-none-any.whl → 0.4.3__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.
- onako/__init__.py +1 -1
- onako/cli.py +49 -0
- onako/static/index.html +82 -9
- onako/tmux_orchestrator.py +15 -6
- {onako-0.4.1.dist-info → onako-0.4.3.dist-info}/METADATA +1 -1
- onako-0.4.3.dist-info/RECORD +10 -0
- onako-0.4.1.dist-info/RECORD +0 -10
- {onako-0.4.1.dist-info → onako-0.4.3.dist-info}/WHEEL +0 -0
- {onako-0.4.1.dist-info → onako-0.4.3.dist-info}/entry_points.txt +0 -0
- {onako-0.4.1.dist-info → onako-0.4.3.dist-info}/top_level.txt +0 -0
onako/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.4.
|
|
1
|
+
__version__ = "0.4.3"
|
onako/cli.py
CHANGED
|
@@ -125,6 +125,55 @@ def stop():
|
|
|
125
125
|
click.echo("Onako service is not running.")
|
|
126
126
|
|
|
127
127
|
|
|
128
|
+
@main.command()
|
|
129
|
+
@click.option("--session", default="onako", help="tmux session name.")
|
|
130
|
+
def purge(session):
|
|
131
|
+
"""Kill all tmux windows from the onako session and clean up."""
|
|
132
|
+
# Auto-detect current tmux session if inside one
|
|
133
|
+
if os.environ.get("TMUX"):
|
|
134
|
+
try:
|
|
135
|
+
result = subprocess.run(
|
|
136
|
+
["tmux", "display-message", "-p", "#S"],
|
|
137
|
+
capture_output=True, text=True,
|
|
138
|
+
)
|
|
139
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
140
|
+
session = result.stdout.strip()
|
|
141
|
+
except FileNotFoundError:
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
# Also stop the server if it's running
|
|
145
|
+
if PID_FILE.exists():
|
|
146
|
+
try:
|
|
147
|
+
pid = int(PID_FILE.read_text().strip())
|
|
148
|
+
os.kill(pid, 15)
|
|
149
|
+
click.echo(f"Stopped onako server (pid {pid}).")
|
|
150
|
+
except (ValueError, ProcessLookupError):
|
|
151
|
+
pass
|
|
152
|
+
PID_FILE.unlink(missing_ok=True)
|
|
153
|
+
|
|
154
|
+
# Kill the tmux session
|
|
155
|
+
result = subprocess.run(
|
|
156
|
+
["tmux", "kill-session", "-t", f"{session}:"],
|
|
157
|
+
capture_output=True, text=True,
|
|
158
|
+
)
|
|
159
|
+
if result.returncode == 0:
|
|
160
|
+
click.echo(f"Killed tmux session: {session}")
|
|
161
|
+
else:
|
|
162
|
+
click.echo(f"No tmux session '{session}' found.")
|
|
163
|
+
|
|
164
|
+
# Clean up the database
|
|
165
|
+
db_path = ONAKO_DIR / "onako.db"
|
|
166
|
+
if db_path.exists():
|
|
167
|
+
import sqlite3
|
|
168
|
+
conn = sqlite3.connect(db_path)
|
|
169
|
+
conn.execute("UPDATE tasks SET status = 'done' WHERE status = 'running'")
|
|
170
|
+
conn.commit()
|
|
171
|
+
conn.close()
|
|
172
|
+
click.echo("Marked all running tasks as done.")
|
|
173
|
+
|
|
174
|
+
click.echo("Purge complete.")
|
|
175
|
+
|
|
176
|
+
|
|
128
177
|
@main.command()
|
|
129
178
|
def status():
|
|
130
179
|
"""Check if Onako is running."""
|
onako/static/index.html
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
cursor: pointer;
|
|
41
41
|
}
|
|
42
42
|
.task-item:hover { background: #222; }
|
|
43
|
+
.task-item.selected { background: #2a2a2a; }
|
|
43
44
|
.task-item .task-id { font-size: 12px; color: #888; }
|
|
44
45
|
.task-item .task-prompt {
|
|
45
46
|
font-size: 14px;
|
|
@@ -83,6 +84,14 @@
|
|
|
83
84
|
#detail-view { display: none; flex-direction: column; height: 100dvh; }
|
|
84
85
|
#detail-view.active { display: flex; }
|
|
85
86
|
#list-view.hidden { display: none; }
|
|
87
|
+
#detail-empty {
|
|
88
|
+
display: none;
|
|
89
|
+
flex: 1;
|
|
90
|
+
align-items: center;
|
|
91
|
+
justify-content: center;
|
|
92
|
+
color: #555;
|
|
93
|
+
font-size: 14px;
|
|
94
|
+
}
|
|
86
95
|
#detail-header {
|
|
87
96
|
padding: 12px 16px;
|
|
88
97
|
padding-top: max(12px, env(safe-area-inset-top));
|
|
@@ -212,12 +221,51 @@
|
|
|
212
221
|
font-size: 16px;
|
|
213
222
|
margin-top: 12px;
|
|
214
223
|
}
|
|
224
|
+
|
|
225
|
+
/* Desktop sidebar layout */
|
|
226
|
+
@media (min-width: 768px) {
|
|
227
|
+
#app-container {
|
|
228
|
+
display: flex;
|
|
229
|
+
height: 100dvh;
|
|
230
|
+
}
|
|
231
|
+
#list-view,
|
|
232
|
+
#list-view.hidden {
|
|
233
|
+
width: 280px;
|
|
234
|
+
flex-shrink: 0;
|
|
235
|
+
border-right: 1px solid #333;
|
|
236
|
+
display: flex;
|
|
237
|
+
flex-direction: column;
|
|
238
|
+
overflow: hidden;
|
|
239
|
+
}
|
|
240
|
+
.task-list {
|
|
241
|
+
flex: 1;
|
|
242
|
+
overflow-y: auto;
|
|
243
|
+
}
|
|
244
|
+
#detail-view {
|
|
245
|
+
flex: 1;
|
|
246
|
+
display: flex;
|
|
247
|
+
height: auto;
|
|
248
|
+
}
|
|
249
|
+
#detail-view:not(.active) #detail-header,
|
|
250
|
+
#detail-view:not(.active) #output,
|
|
251
|
+
#detail-view:not(.active) #message-bar {
|
|
252
|
+
display: none;
|
|
253
|
+
}
|
|
254
|
+
#detail-view:not(.active) #detail-empty {
|
|
255
|
+
display: flex;
|
|
256
|
+
}
|
|
257
|
+
#detail-view.active #detail-empty {
|
|
258
|
+
display: none;
|
|
259
|
+
}
|
|
260
|
+
#back-btn { display: none; }
|
|
261
|
+
}
|
|
215
262
|
</style>
|
|
216
263
|
</head>
|
|
217
264
|
<body>
|
|
218
265
|
<div id="connection-banner">Connection lost. Retrying...</div>
|
|
219
266
|
|
|
220
|
-
|
|
267
|
+
<div id="app-container">
|
|
268
|
+
<!-- List View / Sidebar -->
|
|
221
269
|
<div id="list-view">
|
|
222
270
|
<header>
|
|
223
271
|
<h1>Onako</h1>
|
|
@@ -231,14 +279,18 @@
|
|
|
231
279
|
<div id="detail-header">
|
|
232
280
|
<button id="back-btn">← Back</button>
|
|
233
281
|
<span id="detail-task-id"></span>
|
|
234
|
-
<
|
|
235
|
-
|
|
282
|
+
<div style="display:flex;gap:8px">
|
|
283
|
+
<button id="interrupt-btn">Interrupt</button>
|
|
284
|
+
<button id="kill-btn">Kill</button>
|
|
285
|
+
</div>
|
|
236
286
|
</div>
|
|
237
287
|
<div id="output"></div>
|
|
238
288
|
<div id="message-bar">
|
|
239
289
|
<input id="message-input" type="text" placeholder="Send a message...">
|
|
240
290
|
<button id="send-btn">Send</button>
|
|
241
291
|
</div>
|
|
292
|
+
<div id="detail-empty">Select a task to view</div>
|
|
293
|
+
</div>
|
|
242
294
|
</div>
|
|
243
295
|
|
|
244
296
|
<!-- New Task Modal -->
|
|
@@ -260,6 +312,10 @@
|
|
|
260
312
|
let pollInterval = null;
|
|
261
313
|
let skipPermissionsDefault = false;
|
|
262
314
|
|
|
315
|
+
function isDesktop() {
|
|
316
|
+
return window.innerWidth >= 768;
|
|
317
|
+
}
|
|
318
|
+
|
|
263
319
|
function timeAgo(dateStr) {
|
|
264
320
|
if (!dateStr) return '';
|
|
265
321
|
const seconds = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000);
|
|
@@ -294,7 +350,7 @@
|
|
|
294
350
|
list.innerHTML = '<div class="empty-state">No tasks running</div>';
|
|
295
351
|
} else {
|
|
296
352
|
list.innerHTML = tasks.map(t => `
|
|
297
|
-
<div class="task-item" onclick="showTask('${t.id}')">
|
|
353
|
+
<div class="task-item${currentTaskId === t.id ? ' selected' : ''}" onclick="showTask('${t.id}')">
|
|
298
354
|
<div class="task-id">${t.id}${t.origin === 'external' ? '<span class="origin-badge">external</span>' : ''}</div>
|
|
299
355
|
<div class="task-prompt">${escapeHtml(t.prompt)}</div>
|
|
300
356
|
<div class="task-meta">
|
|
@@ -313,9 +369,19 @@
|
|
|
313
369
|
async function showTask(id) {
|
|
314
370
|
currentTaskId = id;
|
|
315
371
|
currentTaskStatus = null;
|
|
316
|
-
document.getElementById('list-view').classList.add('hidden');
|
|
317
|
-
document.getElementById('detail-view').classList.add('active');
|
|
318
372
|
document.getElementById('detail-task-id').textContent = id;
|
|
373
|
+
if (isDesktop()) {
|
|
374
|
+
document.getElementById('detail-view').classList.add('active');
|
|
375
|
+
document.querySelectorAll('.task-item').forEach(el => el.classList.remove('selected'));
|
|
376
|
+
document.querySelectorAll('.task-item').forEach(el => {
|
|
377
|
+
if (el.onclick && el.getAttribute('onclick')?.includes(id)) {
|
|
378
|
+
el.classList.add('selected');
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
} else {
|
|
382
|
+
document.getElementById('list-view').classList.add('hidden');
|
|
383
|
+
document.getElementById('detail-view').classList.add('active');
|
|
384
|
+
}
|
|
319
385
|
if (id === 'onako-main') {
|
|
320
386
|
document.getElementById('interrupt-btn').classList.add('hidden');
|
|
321
387
|
document.getElementById('kill-btn').classList.add('hidden');
|
|
@@ -323,6 +389,7 @@
|
|
|
323
389
|
document.getElementById('interrupt-btn').classList.remove('hidden');
|
|
324
390
|
document.getElementById('kill-btn').classList.remove('hidden');
|
|
325
391
|
}
|
|
392
|
+
if (pollInterval) clearInterval(pollInterval);
|
|
326
393
|
await refreshOutput();
|
|
327
394
|
pollInterval = setInterval(refreshOutput, 3000);
|
|
328
395
|
}
|
|
@@ -359,8 +426,13 @@
|
|
|
359
426
|
currentTaskStatus = null;
|
|
360
427
|
if (pollInterval) clearInterval(pollInterval);
|
|
361
428
|
pollInterval = null;
|
|
362
|
-
|
|
363
|
-
|
|
429
|
+
if (isDesktop()) {
|
|
430
|
+
document.getElementById('detail-view').classList.remove('active');
|
|
431
|
+
document.querySelectorAll('.task-item').forEach(el => el.classList.remove('selected'));
|
|
432
|
+
} else {
|
|
433
|
+
document.getElementById('detail-view').classList.remove('active');
|
|
434
|
+
document.getElementById('list-view').classList.remove('hidden');
|
|
435
|
+
}
|
|
364
436
|
loadTasks();
|
|
365
437
|
}
|
|
366
438
|
|
|
@@ -429,6 +501,7 @@
|
|
|
429
501
|
document.getElementById('prompt-input').value = '';
|
|
430
502
|
document.getElementById('workdir-input').value = '';
|
|
431
503
|
hideModal();
|
|
504
|
+
await loadTasks();
|
|
432
505
|
showTask(task.id);
|
|
433
506
|
} catch (e) {
|
|
434
507
|
showConnectionError(true);
|
|
@@ -459,7 +532,7 @@
|
|
|
459
532
|
// Init
|
|
460
533
|
loadConfig();
|
|
461
534
|
loadTasks();
|
|
462
|
-
setInterval(() => { if (!currentTaskId) loadTasks(); }, 10000);
|
|
535
|
+
setInterval(() => { if (!currentTaskId || isDesktop()) loadTasks(); }, 10000);
|
|
463
536
|
</script>
|
|
464
537
|
</body>
|
|
465
538
|
</html>
|
onako/tmux_orchestrator.py
CHANGED
|
@@ -21,9 +21,14 @@ class TmuxOrchestrator:
|
|
|
21
21
|
self._ensure_session()
|
|
22
22
|
self.rediscover_tasks()
|
|
23
23
|
|
|
24
|
+
@property
|
|
25
|
+
def _session_target(self) -> str:
|
|
26
|
+
"""Session name with colon suffix for unambiguous tmux targeting."""
|
|
27
|
+
return f"{self.session_name}:"
|
|
28
|
+
|
|
24
29
|
def _ensure_session(self):
|
|
25
30
|
result = subprocess.run(
|
|
26
|
-
["tmux", "has-session", "-t", self.
|
|
31
|
+
["tmux", "has-session", "-t", self._session_target],
|
|
27
32
|
capture_output=True,
|
|
28
33
|
)
|
|
29
34
|
if result.returncode != 0:
|
|
@@ -88,11 +93,11 @@ class TmuxOrchestrator:
|
|
|
88
93
|
self._ensure_session()
|
|
89
94
|
task_id = f"task-{secrets.token_hex(4)}"
|
|
90
95
|
self._run_tmux(
|
|
91
|
-
"new-window", "-t", self.
|
|
96
|
+
"new-window", "-t", self._session_target, "-n", task_id,
|
|
92
97
|
)
|
|
93
98
|
# Look up the window ID for safe targeting
|
|
94
99
|
result = self._run_tmux(
|
|
95
|
-
"list-windows", "-t", self.
|
|
100
|
+
"list-windows", "-t", self._session_target, "-F", "#{window_name}|#{window_id}",
|
|
96
101
|
)
|
|
97
102
|
if result.stdout.strip():
|
|
98
103
|
for line in result.stdout.strip().split("\n"):
|
|
@@ -107,7 +112,11 @@ class TmuxOrchestrator:
|
|
|
107
112
|
)
|
|
108
113
|
self._run_tmux(
|
|
109
114
|
"send-keys", "-t", self._task_target(task_id),
|
|
110
|
-
|
|
115
|
+
"-l", command,
|
|
116
|
+
)
|
|
117
|
+
self._run_tmux(
|
|
118
|
+
"send-keys", "-t", self._task_target(task_id),
|
|
119
|
+
"Enter",
|
|
111
120
|
)
|
|
112
121
|
task = {
|
|
113
122
|
"id": task_id,
|
|
@@ -179,7 +188,7 @@ class TmuxOrchestrator:
|
|
|
179
188
|
|
|
180
189
|
def _sync_task_status(self):
|
|
181
190
|
result = self._run_tmux(
|
|
182
|
-
"list-windows", "-t", self.
|
|
191
|
+
"list-windows", "-t", self._session_target, "-F", "#{window_name}|#{window_id}",
|
|
183
192
|
)
|
|
184
193
|
active_windows = set()
|
|
185
194
|
if result.stdout.strip():
|
|
@@ -196,7 +205,7 @@ class TmuxOrchestrator:
|
|
|
196
205
|
def rediscover_tasks(self):
|
|
197
206
|
"""Rediscover tasks from existing tmux windows on server restart."""
|
|
198
207
|
result = self._run_tmux(
|
|
199
|
-
"list-windows", "-t", self.
|
|
208
|
+
"list-windows", "-t", self._session_target, "-F", "#{window_name}|#{window_id}",
|
|
200
209
|
)
|
|
201
210
|
if not result.stdout.strip():
|
|
202
211
|
return
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
onako/__init__.py,sha256=Nyg0pmk5ea9-SLCAFEIF96ByFx4-TJFtrqYPN-Zn6g4,22
|
|
2
|
+
onako/cli.py,sha256=wcrpaTA1GZNlLghBmrgEnuYzQIDyBte0NqpLhY0mX6w,9541
|
|
3
|
+
onako/server.py,sha256=NfP2CaecxAzuq0tSIaatfYX3d98EuLfhIwK00oHNjro,2883
|
|
4
|
+
onako/tmux_orchestrator.py,sha256=5RPtZ3B0dBeKMEJjYLI54AIJaOVncSp6los2CJ65szA,8572
|
|
5
|
+
onako/static/index.html,sha256=6fXfObv2Gtj3A7yBWYtiee-fdfDXtTNl092i7PiRebY,19383
|
|
6
|
+
onako-0.4.3.dist-info/METADATA,sha256=mfgLx5hEXot1M83Xe93J8LY0srayEwQ4z41raSTfhMY,2175
|
|
7
|
+
onako-0.4.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
+
onako-0.4.3.dist-info/entry_points.txt,sha256=51KRJzuVpr69iT_k4JO0Lj3DQv_HbgtGjTBTev13JAQ,41
|
|
9
|
+
onako-0.4.3.dist-info/top_level.txt,sha256=EZsc5qq2paM9GTbaFE9Xar4B5wFdfIqK9l_bDQVcmZ4,6
|
|
10
|
+
onako-0.4.3.dist-info/RECORD,,
|
onako-0.4.1.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
onako/__init__.py,sha256=pMtTmSUht-XtbR_7Doz6bsQqopJJd8rZ8I8zy2HwwoA,22
|
|
2
|
-
onako/cli.py,sha256=_-dWcf7fVD3QmN6wIzbFLydEVX2pzGs2cKBwP6SZWtY,7932
|
|
3
|
-
onako/server.py,sha256=NfP2CaecxAzuq0tSIaatfYX3d98EuLfhIwK00oHNjro,2883
|
|
4
|
-
onako/tmux_orchestrator.py,sha256=URAOdYo88SzW1ef0o9_hKxJ0SeTtuqZlex1kgTMv42w,8277
|
|
5
|
-
onako/static/index.html,sha256=ioombLZg8uhVHL64saB50ikYBUYNDgw5yClA-QoNxEo,16802
|
|
6
|
-
onako-0.4.1.dist-info/METADATA,sha256=WBaCh-ead_JtD_avuSMhUeJD0dnBl7kLor7UIXPFrB4,2175
|
|
7
|
-
onako-0.4.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
-
onako-0.4.1.dist-info/entry_points.txt,sha256=51KRJzuVpr69iT_k4JO0Lj3DQv_HbgtGjTBTev13JAQ,41
|
|
9
|
-
onako-0.4.1.dist-info/top_level.txt,sha256=EZsc5qq2paM9GTbaFE9Xar4B5wFdfIqK9l_bDQVcmZ4,6
|
|
10
|
-
onako-0.4.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|