onako 0.4.2__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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.4.2"
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
- <!-- List View -->
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">&larr; Back</button>
233
281
  <span id="detail-task-id"></span>
234
- <button id="interrupt-btn">Interrupt</button>
235
- <button id="kill-btn">Kill</button>
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
- document.getElementById('detail-view').classList.remove('active');
363
- document.getElementById('list-view').classList.remove('hidden');
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
- command, "Enter",
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: onako
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Dispatch and monitor Claude Code tasks from your phone
5
5
  Author: Amir
6
6
  License-Expression: MIT
@@ -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,,
@@ -1,10 +0,0 @@
1
- onako/__init__.py,sha256=6hfVa12Q-nXyUEXr6SyKpqPEDJW6vlRHyPxlA27PfTs,22
2
- onako/cli.py,sha256=_-dWcf7fVD3QmN6wIzbFLydEVX2pzGs2cKBwP6SZWtY,7932
3
- onako/server.py,sha256=NfP2CaecxAzuq0tSIaatfYX3d98EuLfhIwK00oHNjro,2883
4
- onako/tmux_orchestrator.py,sha256=QShgrLf0DtYrgXq3a4FxLIBCnq_CZ6skNNuiXcHQvCk,8461
5
- onako/static/index.html,sha256=ioombLZg8uhVHL64saB50ikYBUYNDgw5yClA-QoNxEo,16802
6
- onako-0.4.2.dist-info/METADATA,sha256=1v84RX4BpHeleN7txOm_R2Vy_zFZ4ccSkEhxpbAtFxo,2175
7
- onako-0.4.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
8
- onako-0.4.2.dist-info/entry_points.txt,sha256=51KRJzuVpr69iT_k4JO0Lj3DQv_HbgtGjTBTev13JAQ,41
9
- onako-0.4.2.dist-info/top_level.txt,sha256=EZsc5qq2paM9GTbaFE9Xar4B5wFdfIqK9l_bDQVcmZ4,6
10
- onako-0.4.2.dist-info/RECORD,,
File without changes