onako 0.4.2__tar.gz → 0.4.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.
- {onako-0.4.2 → onako-0.4.3}/PKG-INFO +1 -1
- {onako-0.4.2 → onako-0.4.3}/pyproject.toml +1 -1
- onako-0.4.3/src/onako/__init__.py +1 -0
- {onako-0.4.2 → onako-0.4.3}/src/onako/cli.py +49 -0
- {onako-0.4.2 → onako-0.4.3}/src/onako/static/index.html +82 -9
- {onako-0.4.2 → onako-0.4.3}/src/onako/tmux_orchestrator.py +5 -1
- {onako-0.4.2 → onako-0.4.3}/src/onako.egg-info/PKG-INFO +1 -1
- onako-0.4.2/src/onako/__init__.py +0 -1
- {onako-0.4.2 → onako-0.4.3}/README.md +0 -0
- {onako-0.4.2 → onako-0.4.3}/setup.cfg +0 -0
- {onako-0.4.2 → onako-0.4.3}/src/onako/server.py +0 -0
- {onako-0.4.2 → onako-0.4.3}/src/onako.egg-info/SOURCES.txt +0 -0
- {onako-0.4.2 → onako-0.4.3}/src/onako.egg-info/dependency_links.txt +0 -0
- {onako-0.4.2 → onako-0.4.3}/src/onako.egg-info/entry_points.txt +0 -0
- {onako-0.4.2 → onako-0.4.3}/src/onako.egg-info/requires.txt +0 -0
- {onako-0.4.2 → onako-0.4.3}/src/onako.egg-info/top_level.txt +0 -0
- {onako-0.4.2 → onako-0.4.3}/tests/test_api.py +0 -0
- {onako-0.4.2 → onako-0.4.3}/tests/test_cli.py +0 -0
- {onako-0.4.2 → onako-0.4.3}/tests/test_cli_service.py +0 -0
- {onako-0.4.2 → onako-0.4.3}/tests/test_tmux_orchestrator.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.3"
|
|
@@ -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."""
|
|
@@ -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>
|
|
@@ -112,7 +112,11 @@ class TmuxOrchestrator:
|
|
|
112
112
|
)
|
|
113
113
|
self._run_tmux(
|
|
114
114
|
"send-keys", "-t", self._task_target(task_id),
|
|
115
|
-
|
|
115
|
+
"-l", command,
|
|
116
|
+
)
|
|
117
|
+
self._run_tmux(
|
|
118
|
+
"send-keys", "-t", self._task_target(task_id),
|
|
119
|
+
"Enter",
|
|
116
120
|
)
|
|
117
121
|
task = {
|
|
118
122
|
"id": task_id,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|