onako 0.3.0__py3-none-any.whl → 0.4.1__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.3.0"
1
+ __version__ = "0.4.1"
onako/cli.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import os
2
- import platform
3
2
  import socket
4
3
  import shutil
5
4
  import subprocess
@@ -18,8 +17,9 @@ PID_FILE = ONAKO_DIR / "onako.pid"
18
17
  @click.option("--port", default=8787, type=int, help="Port to bind to.")
19
18
  @click.option("--session", default="onako", help="tmux session name (auto-detected if inside tmux).")
20
19
  @click.option("--dir", "working_dir", default=None, type=click.Path(exists=True), help="Working directory for tasks (default: current directory).")
20
+ @click.option("--dangerously-skip-permissions", "skip_permissions", is_flag=True, default=False, help="Pass --dangerously-skip-permissions to all Claude Code tasks.")
21
21
  @click.pass_context
22
- def main(ctx, host, port, session, working_dir):
22
+ def main(ctx, host, port, session, working_dir, skip_permissions):
23
23
  """Onako — Dispatch and monitor Claude Code tasks from your phone."""
24
24
  if ctx.invoked_subcommand is not None:
25
25
  return
@@ -41,10 +41,12 @@ def main(ctx, host, port, session, working_dir):
41
41
  except FileNotFoundError:
42
42
  pass
43
43
 
44
- _start_server(host, port, session, working_dir)
44
+ _start_server(host, port, session, working_dir, skip_permissions)
45
45
 
46
- # If not inside tmux, ensure session exists and attach
47
- if not os.environ.get("TMUX"):
46
+ inside_tmux = bool(os.environ.get("TMUX"))
47
+
48
+ # If not inside tmux, ensure session exists
49
+ if not inside_tmux:
48
50
  result = subprocess.run(
49
51
  ["tmux", "has-session", "-t", session],
50
52
  capture_output=True,
@@ -54,6 +56,16 @@ def main(ctx, host, port, session, working_dir):
54
56
  ["tmux", "new-session", "-d", "-s", session],
55
57
  check=True,
56
58
  )
59
+
60
+ # Show dashboard URL in tmux status bar
61
+ local_ip = _get_local_ip() or "localhost"
62
+ subprocess.run(
63
+ ["tmux", "set", "-t", session, "status-right",
64
+ f"onako dash: http://{local_ip}:{port}"],
65
+ capture_output=True,
66
+ )
67
+
68
+ if not inside_tmux:
57
69
  os.execvp("tmux", ["tmux", "attach-session", "-t", session])
58
70
 
59
71
 
@@ -69,19 +81,17 @@ def version():
69
81
  @click.option("--port", default=8787, type=int, help="Port to bind to.")
70
82
  @click.option("--session", default="onako", help="tmux session name.")
71
83
  @click.option("--dir", "working_dir", default=None, type=click.Path(exists=True), help="Working directory for tasks (default: current directory).")
72
- @click.option("--background", is_flag=True, help="Run as a background service (launchd/systemd).")
73
- def serve(host, port, session, working_dir, background):
84
+ @click.option("--dangerously-skip-permissions", "skip_permissions", is_flag=True, default=False, help="Pass --dangerously-skip-permissions to all Claude Code tasks.")
85
+ def serve(host, port, session, working_dir, skip_permissions):
74
86
  """Start the Onako server."""
75
87
  working_dir = str(Path(working_dir).resolve()) if working_dir else os.getcwd()
76
88
 
77
- if background:
78
- _start_background(host, port, working_dir)
79
- return
80
-
81
89
  _check_prerequisites()
82
90
 
83
91
  os.environ["ONAKO_WORKING_DIR"] = working_dir
84
92
  os.environ["ONAKO_SESSION"] = session
93
+ if skip_permissions:
94
+ os.environ["ONAKO_SKIP_PERMISSIONS"] = "1"
85
95
 
86
96
  from onako import __version__
87
97
  click.echo(f"Onako v{__version__}")
@@ -111,31 +121,6 @@ def stop():
111
121
  click.echo("Stale pid file found, cleaning up.")
112
122
  PID_FILE.unlink(missing_ok=True)
113
123
 
114
- # Fall back to launchd/systemd
115
- if not stopped:
116
- system = platform.system()
117
- if system == "Darwin":
118
- plist_path = Path.home() / "Library" / "LaunchAgents" / "com.onako.server.plist"
119
- if plist_path.exists():
120
- uid = os.getuid()
121
- result = subprocess.run(
122
- ["launchctl", "bootout", f"gui/{uid}", str(plist_path)],
123
- capture_output=True,
124
- )
125
- if result.returncode != 0:
126
- subprocess.run(["launchctl", "unload", str(plist_path)], capture_output=True)
127
- plist_path.unlink()
128
- click.echo("Onako service stopped and removed.")
129
- stopped = True
130
- elif system == "Linux":
131
- unit_path = Path.home() / ".config" / "systemd" / "user" / "onako.service"
132
- if unit_path.exists():
133
- subprocess.run(["systemctl", "--user", "disable", "--now", "onako"])
134
- unit_path.unlink()
135
- subprocess.run(["systemctl", "--user", "daemon-reload"])
136
- click.echo("Onako service stopped and removed.")
137
- stopped = True
138
-
139
124
  if not stopped:
140
125
  click.echo("Onako service is not running.")
141
126
 
@@ -169,7 +154,7 @@ def _is_server_running():
169
154
  return False
170
155
 
171
156
 
172
- def _start_server(host, port, session, working_dir):
157
+ def _start_server(host, port, session, working_dir, skip_permissions=False):
173
158
  """Start the Onako server in the background if not already running.
174
159
 
175
160
  Returns True if the server was started or is already running.
@@ -186,9 +171,13 @@ def _start_server(host, port, session, working_dir):
186
171
 
187
172
  log_out = LOG_DIR / "onako.log"
188
173
 
174
+ cmd = [onako_bin, "serve", "--host", host, "--port", str(port), "--session", session, "--dir", working_dir]
175
+ if skip_permissions:
176
+ cmd.append("--dangerously-skip-permissions")
177
+
189
178
  with open(log_out, "a") as log_fh:
190
179
  proc = subprocess.Popen(
191
- [onako_bin, "serve", "--host", host, "--port", str(port), "--session", session, "--dir", working_dir],
180
+ cmd,
192
181
  stdout=log_fh,
193
182
  stderr=subprocess.STDOUT,
194
183
  start_new_session=True,
@@ -222,98 +211,6 @@ def _start_server(host, port, session, working_dir):
222
211
 
223
212
 
224
213
 
225
- def _start_background(host, port, working_dir):
226
- """Install and start Onako as a background service."""
227
- system = platform.system()
228
- onako_bin = shutil.which("onako")
229
- if not onako_bin:
230
- click.echo("Error: 'onako' command not found on PATH.", err=True)
231
- sys.exit(1)
232
-
233
- LOG_DIR.mkdir(parents=True, exist_ok=True)
234
-
235
- # Build PATH that includes dirs for tmux and claude
236
- path_dirs = set()
237
- for cmd in ["tmux", "claude"]:
238
- p = shutil.which(cmd)
239
- if p:
240
- path_dirs.add(str(Path(p).parent))
241
- path_dirs.update(["/usr/local/bin", "/usr/bin", "/bin"])
242
- path_value = ":".join(sorted(path_dirs))
243
-
244
- if system == "Darwin":
245
- _install_launchd(onako_bin, host, port, working_dir, path_value)
246
- elif system == "Linux":
247
- _install_systemd(onako_bin, host, port, working_dir, path_value)
248
- else:
249
- click.echo(f"Background mode is not supported on {system}. Run 'onako serve' manually.", err=True)
250
- sys.exit(1)
251
-
252
-
253
- def _install_launchd(onako_bin, host, port, working_dir, path_value):
254
- from importlib.resources import files
255
- tpl = files("onako").joinpath("templates", "com.onako.server.plist.tpl").read_text()
256
- plist = tpl.format(
257
- onako_bin=onako_bin,
258
- host=host,
259
- port=port,
260
- working_dir=working_dir,
261
- log_dir=LOG_DIR,
262
- path_value=path_value,
263
- )
264
- plist_path = Path.home() / "Library" / "LaunchAgents" / "com.onako.server.plist"
265
- plist_path.parent.mkdir(parents=True, exist_ok=True)
266
-
267
- # Unload existing service if present
268
- uid = os.getuid()
269
- subprocess.run(
270
- ["launchctl", "bootout", f"gui/{uid}", str(plist_path)],
271
- capture_output=True,
272
- )
273
-
274
- plist_path.write_text(plist)
275
-
276
- # Register and start the service
277
- result = subprocess.run(
278
- ["launchctl", "bootstrap", f"gui/{uid}", str(plist_path)],
279
- capture_output=True, text=True,
280
- )
281
- if result.returncode != 0:
282
- subprocess.run(["launchctl", "load", str(plist_path)], check=True)
283
-
284
- # kickstart forces the service to run now (bootstrap alone may not start it)
285
- subprocess.run(
286
- ["launchctl", "kickstart", f"gui/{uid}/com.onako.server"],
287
- capture_output=True,
288
- )
289
-
290
- click.echo(f"Onako running in background at http://{host}:{port}")
291
- click.echo(f" Working directory: {working_dir}")
292
- click.echo(f" Logs: {LOG_DIR}")
293
- click.echo()
294
- click.echo("If macOS blocks this service, allow it in:")
295
- click.echo(" System Settings > General > Login Items & Extensions")
296
-
297
-
298
- def _install_systemd(onako_bin, host, port, working_dir, path_value):
299
- from importlib.resources import files
300
- tpl = files("onako").joinpath("templates", "onako.service.tpl").read_text()
301
- unit = tpl.format(
302
- onako_bin=onako_bin,
303
- host=host,
304
- port=port,
305
- working_dir=working_dir,
306
- path_value=path_value,
307
- )
308
- unit_path = Path.home() / ".config" / "systemd" / "user" / "onako.service"
309
- unit_path.parent.mkdir(parents=True, exist_ok=True)
310
- unit_path.write_text(unit)
311
- subprocess.run(["systemctl", "--user", "daemon-reload"], check=True)
312
- subprocess.run(["systemctl", "--user", "enable", "--now", "onako"], check=True)
313
- click.echo(f"Onako running in background at http://{host}:{port}")
314
- click.echo(f" Working directory: {working_dir}")
315
-
316
-
317
214
  def _get_local_ip():
318
215
  """Get the machine's local network IP address."""
319
216
  try:
onako/server.py CHANGED
@@ -9,6 +9,7 @@ from onako.tmux_orchestrator import TmuxOrchestrator
9
9
 
10
10
  app = FastAPI()
11
11
  session_name = os.environ.get("ONAKO_SESSION", "onako")
12
+ skip_permissions_default = os.environ.get("ONAKO_SKIP_PERMISSIONS") == "1"
12
13
  orch = TmuxOrchestrator(session_name=session_name)
13
14
  static_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")
14
15
 
@@ -16,6 +17,7 @@ static_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static")
16
17
  class CreateTaskRequest(BaseModel):
17
18
  prompt: str
18
19
  working_dir: str | None = None
20
+ skip_permissions: bool | None = None
19
21
 
20
22
 
21
23
  class SendMessageRequest(BaseModel):
@@ -29,12 +31,16 @@ def dashboard():
29
31
 
30
32
  @app.get("/health")
31
33
  def health():
32
- return {"status": "ok"}
34
+ return {"status": "ok", "skip_permissions": skip_permissions_default}
33
35
 
34
36
 
35
37
  @app.post("/tasks")
36
38
  def create_task(req: CreateTaskRequest):
37
- command = f"claude {shlex.quote(req.prompt)}"
39
+ skip = req.skip_permissions if req.skip_permissions is not None else skip_permissions_default
40
+ if skip:
41
+ command = f"claude --dangerously-skip-permissions {shlex.quote(req.prompt)}"
42
+ else:
43
+ command = f"claude {shlex.quote(req.prompt)}"
38
44
  task = orch.create_task(command, working_dir=req.working_dir, prompt=req.prompt)
39
45
  return task
40
46
 
@@ -70,6 +76,14 @@ def send_message(task_id: str, req: SendMessageRequest):
70
76
  return {"status": "sent"}
71
77
 
72
78
 
79
+ @app.post("/tasks/{task_id}/interrupt")
80
+ def interrupt_task(task_id: str):
81
+ if task_id not in orch.tasks:
82
+ raise HTTPException(status_code=404, detail="Task not found")
83
+ orch.send_interrupt(task_id)
84
+ return {"status": "interrupted"}
85
+
86
+
73
87
  @app.delete("/tasks/{task_id}")
74
88
  def delete_task(task_id: str):
75
89
  if task_id not in orch.tasks:
onako/static/index.html CHANGED
@@ -98,6 +98,16 @@
98
98
  cursor: pointer;
99
99
  font-size: 14px;
100
100
  }
101
+ #interrupt-btn {
102
+ background: #f59e0b;
103
+ color: white;
104
+ border: none;
105
+ padding: 6px 12px;
106
+ border-radius: 6px;
107
+ cursor: pointer;
108
+ font-size: 12px;
109
+ }
110
+ #interrupt-btn.hidden { display: none; }
101
111
  #kill-btn {
102
112
  background: #ef4444;
103
113
  color: white;
@@ -221,6 +231,7 @@
221
231
  <div id="detail-header">
222
232
  <button id="back-btn">&larr; Back</button>
223
233
  <span id="detail-task-id"></span>
234
+ <button id="interrupt-btn">Interrupt</button>
224
235
  <button id="kill-btn">Kill</button>
225
236
  </div>
226
237
  <div id="output"></div>
@@ -235,6 +246,10 @@
235
246
  <div id="modal">
236
247
  <textarea id="prompt-input" placeholder="What do you want done?"></textarea>
237
248
  <input id="workdir-input" type="text" placeholder="Working directory (optional)">
249
+ <label id="skip-perms-label" style="display:flex;align-items:center;gap:8px;margin-top:8px;font-size:13px;color:#aaa;cursor:pointer;">
250
+ <input type="checkbox" id="skip-perms-input" style="width:auto;margin:0;">
251
+ Skip permissions
252
+ </label>
238
253
  <button id="submit-task-btn">Start Task</button>
239
254
  </div>
240
255
 
@@ -243,6 +258,7 @@
243
258
  let currentTaskId = null;
244
259
  let currentTaskStatus = null;
245
260
  let pollInterval = null;
261
+ let skipPermissionsDefault = false;
246
262
 
247
263
  function timeAgo(dateStr) {
248
264
  if (!dateStr) return '';
@@ -260,6 +276,15 @@
260
276
  document.getElementById('connection-banner').style.display = show ? 'block' : 'none';
261
277
  }
262
278
 
279
+ async function loadConfig() {
280
+ try {
281
+ const res = await fetch(`${API}/health`);
282
+ const data = await res.json();
283
+ skipPermissionsDefault = data.skip_permissions || false;
284
+ document.getElementById('skip-perms-input').checked = skipPermissionsDefault;
285
+ } catch (e) {}
286
+ }
287
+
263
288
  async function loadTasks() {
264
289
  try {
265
290
  const res = await fetch(`${API}/tasks`);
@@ -292,8 +317,10 @@
292
317
  document.getElementById('detail-view').classList.add('active');
293
318
  document.getElementById('detail-task-id').textContent = id;
294
319
  if (id === 'onako-main') {
320
+ document.getElementById('interrupt-btn').classList.add('hidden');
295
321
  document.getElementById('kill-btn').classList.add('hidden');
296
322
  } else {
323
+ document.getElementById('interrupt-btn').classList.remove('hidden');
297
324
  document.getElementById('kill-btn').classList.remove('hidden');
298
325
  }
299
326
  await refreshOutput();
@@ -315,6 +342,7 @@
315
342
  // Stop polling and hide kill button when task is done
316
343
  if (data.status === 'done' && currentTaskStatus !== 'done') {
317
344
  currentTaskStatus = 'done';
345
+ document.getElementById('interrupt-btn').classList.add('hidden');
318
346
  document.getElementById('kill-btn').classList.add('hidden');
319
347
  if (pollInterval) {
320
348
  clearInterval(pollInterval);
@@ -352,6 +380,15 @@
352
380
  }
353
381
  }
354
382
 
383
+ async function interruptTask() {
384
+ if (!currentTaskId) return;
385
+ try {
386
+ await fetch(`${API}/tasks/${currentTaskId}/interrupt`, {method: 'POST'});
387
+ } catch (e) {
388
+ showConnectionError(true);
389
+ }
390
+ }
391
+
355
392
  async function killTask() {
356
393
  if (!currentTaskId) return;
357
394
  if (!confirm('Kill this task?')) return;
@@ -366,6 +403,7 @@
366
403
  function showModal() {
367
404
  document.getElementById('modal').style.display = 'block';
368
405
  document.getElementById('modal-overlay').style.display = 'block';
406
+ document.getElementById('skip-perms-input').checked = skipPermissionsDefault;
369
407
  document.getElementById('prompt-input').focus();
370
408
  }
371
409
 
@@ -378,7 +416,8 @@
378
416
  const prompt = document.getElementById('prompt-input').value.trim();
379
417
  if (!prompt) return;
380
418
  const workdir = document.getElementById('workdir-input').value.trim() || null;
381
- const body = {prompt};
419
+ const skipPerms = document.getElementById('skip-perms-input').checked;
420
+ const body = {prompt, skip_permissions: skipPerms};
382
421
  if (workdir) body.working_dir = workdir;
383
422
  try {
384
423
  const res = await fetch(`${API}/tasks`, {
@@ -407,6 +446,7 @@
407
446
  document.getElementById('modal-overlay').addEventListener('click', hideModal);
408
447
  document.getElementById('submit-task-btn').addEventListener('click', submitTask);
409
448
  document.getElementById('back-btn').addEventListener('click', showList);
449
+ document.getElementById('interrupt-btn').addEventListener('click', interruptTask);
410
450
  document.getElementById('kill-btn').addEventListener('click', killTask);
411
451
  document.getElementById('send-btn').addEventListener('click', sendMessage);
412
452
  document.getElementById('message-input').addEventListener('keydown', e => {
@@ -417,6 +457,7 @@
417
457
  });
418
458
 
419
459
  // Init
460
+ loadConfig();
420
461
  loadTasks();
421
462
  setInterval(() => { if (!currentTaskId) loadTasks(); }, 10000);
422
463
  </script>
@@ -168,6 +168,9 @@ class TmuxOrchestrator:
168
168
  "Enter",
169
169
  )
170
170
 
171
+ def send_interrupt(self, task_id: str):
172
+ self._run_tmux("send-keys", "-t", self._task_target(task_id), "Escape")
173
+
171
174
  def kill_task(self, task_id: str):
172
175
  self._run_tmux("kill-window", "-t", self._task_target(task_id))
173
176
  if task_id in self.tasks:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: onako
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: Dispatch and monitor Claude Code tasks from your phone
5
5
  Author: Amir
6
6
  License-Expression: MIT
@@ -39,12 +39,20 @@ onako --session my-project # custom session name
39
39
  If you're already inside tmux, onako auto-detects your session and skips the attach. Open http://localhost:8787 on your phone (same network) or set up [Tailscale](https://tailscale.com) for access from anywhere.
40
40
 
41
41
  ```bash
42
- onako stop # stop the background server
42
+ onako stop # stop the server
43
43
  onako status # check if running
44
44
  onako serve # foreground server (for development)
45
45
  onako version # print version
46
46
  ```
47
47
 
48
+ ### Adopting existing tmux windows
49
+
50
+ If you already have work running in another tmux session, move those windows into onako's session so they show up in the dashboard:
51
+
52
+ ```bash
53
+ tmux move-window -s <session>:<window> -t onako
54
+ ```
55
+
48
56
  ## How it works
49
57
 
50
58
  Onako monitors all tmux windows in the configured session. Windows it creates (via the dashboard) are "managed" tasks. Windows created by you or other tools are discovered automatically as "external" — both get full dashboard support: view output, send messages, kill.
@@ -0,0 +1,10 @@
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,,
@@ -1,34 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0">
4
- <dict>
5
- <key>Label</key>
6
- <string>com.onako.server</string>
7
- <key>ProgramArguments</key>
8
- <array>
9
- <string>{onako_bin}</string>
10
- <string>serve</string>
11
- <string>--host</string>
12
- <string>{host}</string>
13
- <string>--port</string>
14
- <string>{port}</string>
15
- <string>--dir</string>
16
- <string>{working_dir}</string>
17
- </array>
18
- <key>WorkingDirectory</key>
19
- <string>{working_dir}</string>
20
- <key>RunAtLoad</key>
21
- <true/>
22
- <key>KeepAlive</key>
23
- <true/>
24
- <key>StandardOutPath</key>
25
- <string>{log_dir}/server.stdout.log</string>
26
- <key>StandardErrorPath</key>
27
- <string>{log_dir}/server.stderr.log</string>
28
- <key>EnvironmentVariables</key>
29
- <dict>
30
- <key>PATH</key>
31
- <string>{path_value}</string>
32
- </dict>
33
- </dict>
34
- </plist>
@@ -1,12 +0,0 @@
1
- [Unit]
2
- Description=Onako - Claude Code Task Orchestrator
3
- After=network.target
4
-
5
- [Service]
6
- ExecStart={onako_bin} serve --host {host} --port {port} --dir {working_dir}
7
- WorkingDirectory={working_dir}
8
- Restart=on-failure
9
- Environment=PATH={path_value}
10
-
11
- [Install]
12
- WantedBy=default.target
@@ -1,12 +0,0 @@
1
- onako/__init__.py,sha256=VrXpHDu3erkzwl_WXrqINBm9xWkcyUy53IQOj042dOs,22
2
- onako/cli.py,sha256=S6Haa3X16s5EaLzbLw6W2W3bO_26jt3utgMB8sohM-k,11786
3
- onako/server.py,sha256=cff5cKRgeItRNLfvPHQ5NMX8eBl5Sesn26G3rac7KyY,2261
4
- onako/tmux_orchestrator.py,sha256=D83eY7iSSgKoZwhd-sjk-JM2bfNE30AyoPAbNwrTZIg,8152
5
- onako/static/index.html,sha256=_l_ELWjtmLBk-5p3b3tjJ0fj83IT4GzJBrWbcaTFKQo,14917
6
- onako/templates/com.onako.server.plist.tpl,sha256=XvjvRz_AnjcREVjDPFu2qGMVCxojp6hhTwMfvrFcEbY,994
7
- onako/templates/onako.service.tpl,sha256=EOVOaLtxC1FrcLdy7DtEbtf9ImgN6PmnMb57ZT81nTM,280
8
- onako-0.3.0.dist-info/METADATA,sha256=_6meanq6QmLs-0ddqo8lCYR4HNXsw66fQXikk23cBoc,1956
9
- onako-0.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10
- onako-0.3.0.dist-info/entry_points.txt,sha256=51KRJzuVpr69iT_k4JO0Lj3DQv_HbgtGjTBTev13JAQ,41
11
- onako-0.3.0.dist-info/top_level.txt,sha256=EZsc5qq2paM9GTbaFE9Xar4B5wFdfIqK9l_bDQVcmZ4,6
12
- onako-0.3.0.dist-info/RECORD,,
File without changes