monoco-toolkit 0.1.1__py3-none-any.whl → 0.1.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.
monoco/core/telemetry.py CHANGED
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import uuid
3
3
  import json
4
- import httpx
4
+
5
5
  import time
6
6
  from pathlib import Path
7
7
  from typing import Optional, Dict, Any
@@ -73,7 +73,10 @@ class Telemetry:
73
73
 
74
74
  # Send asynchronously? For now, we'll do a simple non-blocking-ish call
75
75
  try:
76
+ import httpx
76
77
  httpx.post(f"{POSTHOG_HOST}/capture/", json=data, timeout=1.0)
78
+ except ImportError:
79
+ pass # Telemetry is optional
77
80
  except Exception:
78
81
  pass
79
82
 
@@ -51,7 +51,11 @@ def parse_issue(file_path: Path) -> Optional[IssueMetadata]:
51
51
  data = yaml.safe_load(match.group(1))
52
52
  if not isinstance(data, dict):
53
53
  return None
54
- return IssueMetadata(**data)
54
+
55
+ # Inject path before validation to ensure it persists
56
+ data['path'] = str(file_path.absolute())
57
+ meta = IssueMetadata(**data)
58
+ return meta
55
59
  except Exception:
56
60
  return None
57
61
 
@@ -72,6 +76,8 @@ def parse_issue_detail(file_path: Path) -> Optional[IssueDetail]:
72
76
  data = yaml.safe_load(yaml_str)
73
77
  if not isinstance(data, dict):
74
78
  return None
79
+
80
+ data['path'] = str(file_path.absolute())
75
81
  return IssueDetail(**data, body=body, raw_content=content)
76
82
  except Exception:
77
83
  return None
@@ -161,6 +167,9 @@ def create_issue_file(
161
167
  file_path = target_dir / filename
162
168
  file_path.write_text(file_content)
163
169
 
170
+ # Inject path into returned metadata
171
+ metadata.path = str(file_path.absolute())
172
+
164
173
  return metadata, file_path
165
174
  def validate_transition(
166
175
  current_status: IssueStatus,
@@ -105,7 +105,10 @@ class IssueMetadata(BaseModel):
105
105
  isolation: Optional[IssueIsolation] = None
106
106
  dependencies: List[str] = []
107
107
  related: List[str] = []
108
+ dependencies: List[str] = []
109
+ related: List[str] = []
108
110
  tags: List[str] = []
111
+ path: Optional[str] = None # Absolute path to the issue file
109
112
 
110
113
 
111
114
  @model_validator(mode='before')
@@ -0,0 +1,185 @@
1
+
2
+ import asyncio
3
+ import os
4
+ import pty
5
+ import select
6
+ import signal
7
+ import struct
8
+ import fcntl
9
+ import termios
10
+ import logging
11
+ from typing import Dict, Optional, Tuple, Any
12
+
13
+ logger = logging.getLogger("monoco.pty")
14
+
15
+ class PTYSession:
16
+ """
17
+ Manages a single PTY session connected to a subprocess (shell).
18
+ """
19
+ def __init__(self, session_id: str, cmd: list[str], env: Optional[Dict[str, str]] = None, cwd: Optional[str] = None):
20
+ self.session_id = session_id
21
+ self.cmd = cmd
22
+ self.env = env or os.environ.copy()
23
+ self.cwd = cwd or os.getcwd()
24
+
25
+ self.fd: Optional[int] = None
26
+ self.pid: Optional[int] = None
27
+ self.proc = None # subprocess.Popen object
28
+ self.running = False
29
+ self.loop = asyncio.get_running_loop()
30
+
31
+ def start(self, cols: int = 80, rows: int = 24):
32
+ """
33
+ Spawn a subprocess connected to a new PTY using subprocess.Popen.
34
+ This provides better safety in threaded/asyncio environments than pty.fork().
35
+ """
36
+ import subprocess
37
+
38
+ # 1. Open PTY pair
39
+ master_fd, slave_fd = pty.openpty()
40
+
41
+ # 2. Set initial size
42
+ self._set_winsize(master_fd, rows, cols)
43
+
44
+ try:
45
+ # 3. Spawn process
46
+ # start_new_session=True executes setsid()
47
+ self.proc = subprocess.Popen(
48
+ self.cmd,
49
+ stdin=slave_fd,
50
+ stdout=slave_fd,
51
+ stderr=slave_fd,
52
+ cwd=self.cwd,
53
+ env=self.env,
54
+ start_new_session=True,
55
+ close_fds=True # Important to close other FDs in child
56
+ )
57
+
58
+ self.pid = self.proc.pid
59
+ self.fd = master_fd
60
+ self.running = True
61
+
62
+ # 4. Close slave fd in parent (child has it open now)
63
+ os.close(slave_fd)
64
+
65
+ logger.info(f"Started session {self.session_id} (PID: {self.pid})")
66
+
67
+ except Exception as e:
68
+ logger.error(f"Failed to spawn process: {e}")
69
+ # Ensure we clean up fds if spawn fails
70
+ try:
71
+ os.close(master_fd)
72
+ except: pass
73
+ try:
74
+ os.close(slave_fd)
75
+ except: pass
76
+ raise e
77
+
78
+
79
+ def resize(self, cols: int, rows: int):
80
+ """
81
+ Resize the PTY.
82
+ """
83
+ if self.fd and self.running:
84
+ self._set_winsize(self.fd, rows, cols)
85
+
86
+ def write(self, data: bytes):
87
+ """
88
+ Write input data (from websocket) to the PTY master fd.
89
+ """
90
+ if self.fd and self.running:
91
+ os.write(self.fd, data)
92
+
93
+ async def read(self) -> bytes:
94
+ """
95
+ Read output data from PTY master fd (to forward to websocket).
96
+ """
97
+ if not self.fd or not self.running:
98
+ return b""
99
+
100
+ try:
101
+ # Run in executor to avoid blocking the event loop
102
+ # pty read is blocking
103
+ return await self.loop.run_in_executor(None, self._read_blocking)
104
+ except OSError:
105
+ return b""
106
+
107
+ def _read_blocking(self) -> bytes:
108
+ try:
109
+ return os.read(self.fd, 1024)
110
+ except OSError:
111
+ return b""
112
+
113
+ def terminate(self):
114
+ """
115
+ Terminate the process and close the PTY.
116
+ """
117
+ self.running = False
118
+
119
+ # Use Popen object if available
120
+ if self.proc:
121
+ try:
122
+ self.proc.terminate()
123
+ try:
124
+ self.proc.wait(timeout=1.0)
125
+ except:
126
+ # Force kill if not terminated
127
+ self.proc.kill()
128
+ self.proc.wait()
129
+ except Exception as e:
130
+ logger.error(f"Error terminating process: {e}")
131
+ self.proc = None
132
+ self.pid = None
133
+ elif self.pid:
134
+ # Fallback for legacy or if Popen obj lost
135
+ try:
136
+ os.kill(self.pid, signal.SIGTERM)
137
+ os.waitpid(self.pid, 0) # Reap zombie
138
+ except OSError:
139
+ pass
140
+ self.pid = None
141
+
142
+ if self.fd:
143
+ try:
144
+ os.close(self.fd)
145
+ except OSError:
146
+ pass
147
+ self.fd = None
148
+ logger.info(f"Terminated session {self.session_id}")
149
+
150
+ def _set_winsize(self, fd: int, row: int, col: int, xpix: int = 0, ypix: int = 0):
151
+ winsize = struct.pack("HHHH", row, col, xpix, ypix)
152
+ fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
153
+
154
+
155
+ class PTYManager:
156
+ """
157
+ Singleton to manage multiple PTY sessions.
158
+ """
159
+ def __init__(self):
160
+ self.sessions: Dict[str, PTYSession] = {}
161
+
162
+ def create_session(self, session_id: str, cwd: str, cmd: list[str] = ["/bin/zsh"], env: Dict = None) -> PTYSession:
163
+ if session_id in self.sessions:
164
+ # In a real app, we might want to attach to existing?
165
+ # For now, kill and recreate (or error)
166
+ self.close_session(session_id)
167
+
168
+ session = PTYSession(session_id, cmd, env, cwd)
169
+ self.sessions[session_id] = session
170
+ return session
171
+
172
+ def get_session(self, session_id: str) -> Optional[PTYSession]:
173
+ return self.sessions.get(session_id)
174
+
175
+ def close_session(self, session_id: str):
176
+ if session_id in self.sessions:
177
+ self.sessions[session_id].terminate()
178
+ del self.sessions[session_id]
179
+
180
+ def close_all_sessions(self):
181
+ """
182
+ Terminate all active PTY sessions.
183
+ """
184
+ for session_id in list(self.sessions.keys()):
185
+ self.close_session(session_id)
@@ -0,0 +1,138 @@
1
+
2
+ from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Query
3
+ from pydantic import BaseModel
4
+ from typing import Optional, Dict
5
+ import json
6
+ import asyncio
7
+ import logging
8
+ import os
9
+ from pathlib import Path
10
+ from monoco.features.pty.core import PTYManager
11
+ from monoco.core.config import get_config
12
+
13
+ # We will use dependency injection or a global singleton for now
14
+ # Ideally attached to app state
15
+ pty_manager = PTYManager()
16
+
17
+ router = APIRouter(prefix="/api/v1/pty", tags=["pty"])
18
+
19
+ logger = logging.getLogger("monoco.pty")
20
+
21
+ @router.websocket("/ws/{session_id}")
22
+ async def websocket_pty_endpoint(
23
+ websocket: WebSocket,
24
+ session_id: str,
25
+ cwd: Optional[str] = Query(None),
26
+ cols: int = Query(80),
27
+ rows: int = Query(24),
28
+ env: Optional[str] = Query(None) # JSON-encoded env vars
29
+ ):
30
+ await websocket.accept()
31
+
32
+ # Determine working directory
33
+ # 1. Provide explicit CWD in query
34
+ # 2. Or fallback to ProjectRoot from env (if integrated)
35
+ # 3. Or fallback to process CWD
36
+
37
+ # Since monoco pty runs as a separate service, we expect CWD to be passed
38
+ # or we default to where monoco pty was started
39
+ working_dir = cwd if cwd else os.getcwd()
40
+
41
+ # Prepare environment
42
+ env_vars = os.environ.copy()
43
+ env_vars["TERM"] = "xterm-256color"
44
+ env_vars["COLORTERM"] = "truecolor"
45
+ if "SHELL" not in env_vars:
46
+ env_vars["SHELL"] = "/bin/zsh"
47
+ if "HOME" not in env_vars:
48
+ import pathlib
49
+ env_vars["HOME"] = str(pathlib.Path.home())
50
+
51
+ # Filter out Trae/Gemini specific variables to avoid shell integration conflicts
52
+ # This prevents the shell from trying to write to IDE-specific logs which causes EPERM
53
+ keys_to_remove = [k for k in env_vars.keys() if k.startswith("TRAE_") or k.startswith("GEMINI_") or k == "AI_AGENT"]
54
+ for k in keys_to_remove:
55
+ del env_vars[k]
56
+
57
+ if env:
58
+ try:
59
+ custom_env = json.loads(env)
60
+ env_vars.update(custom_env)
61
+ except:
62
+ logger.warning("Failed to parse custom env vars")
63
+
64
+ # Start Session
65
+ try:
66
+ session = pty_manager.create_session(
67
+ session_id=session_id,
68
+ cwd=working_dir,
69
+ cmd=["/bin/zsh", "-l"], # Use login shell to ensure full user environment
70
+ env=env_vars
71
+ )
72
+ session.start(cols, rows)
73
+ except Exception as e:
74
+ logger.error(f"Failed to start session: {e}")
75
+ await websocket.close(code=1011)
76
+ return
77
+
78
+ # Pipe Loop
79
+ reader_task = None
80
+ try:
81
+ # Task to read from PTY and send to WebSocket
82
+ async def pty_reader():
83
+ while session.running:
84
+ data = await session.read()
85
+ if not data:
86
+ break
87
+ # xterm.js expects string or binary. We send string/bytes.
88
+ # Usually text is fine, but binary is safer for control codes.
89
+ await websocket.send_bytes(data)
90
+
91
+ # If PTY exits, close WS
92
+ await websocket.close()
93
+
94
+ reader_task = asyncio.create_task(pty_reader())
95
+
96
+ # Main loop: Read from WebSocket and write to PTY
97
+ try:
98
+ while True:
99
+ # Receive message from Client (xterm.js)
100
+ # Message can be simple input string, or a JSON command (resize)
101
+ message = await websocket.receive()
102
+
103
+ if message["type"] == "websocket.disconnect":
104
+ raise WebSocketDisconnect(code=message.get("code", 1000))
105
+
106
+ if "text" in message:
107
+ payload = message["text"]
108
+
109
+ # Check if it's a control message (Hack: usually client sends raw input)
110
+ # We can enforce a protocol: binary for Input, text JSON for Control.
111
+ try:
112
+ # Try parsing as JSON control message
113
+ cmd = json.loads(payload)
114
+ if cmd.get("type") == "resize":
115
+ session.resize(cmd["cols"], cmd["rows"])
116
+ continue
117
+ except:
118
+ pass # Not JSON, treat as raw input
119
+
120
+ session.write(payload.encode())
121
+
122
+ elif "bytes" in message:
123
+ session.write(message["bytes"])
124
+ except RuntimeError:
125
+ # Handle "Cannot call 'receive' once a disconnect message has been received"
126
+ # This happens if Starlette/FastAPI already processed the disconnect internally
127
+ # but we called receive() again.
128
+ logger.info(f"Runtime disconnect for session {session_id}")
129
+
130
+ except WebSocketDisconnect:
131
+ logger.info(f"Client disconnected for session {session_id}")
132
+ except Exception as e:
133
+ logger.error(f"WebSocket error: {e}")
134
+ finally:
135
+ # Cleanup
136
+ pty_manager.close_session(session_id)
137
+ if reader_task and not reader_task.done():
138
+ reader_task.cancel()
@@ -0,0 +1,56 @@
1
+ import logging
2
+ import signal
3
+ import sys
4
+ from typing import Optional
5
+ from pathlib import Path
6
+ from contextlib import asynccontextmanager
7
+ import uvicorn
8
+ from fastapi import FastAPI
9
+ from monoco.features.pty.router import router as pty_router, pty_manager
10
+
11
+ @asynccontextmanager
12
+ async def lifespan(app: FastAPI):
13
+ # Startup
14
+ yield
15
+ # Shutdown
16
+ logging.info("Shutting down PTY manager and cleaning up sessions...")
17
+ pty_manager.close_all_sessions()
18
+
19
+ def run_pty_server(host: str = "127.0.0.1", port: int = 3124, cwd: Optional[Path] = None):
20
+ """
21
+ Entry point for the 'monoco pty' command.
22
+ """
23
+ # Configure Logging
24
+ logging.basicConfig(level=logging.INFO)
25
+
26
+ # Register a manual signal handler to ensure we catch termination even if uvicorn misses it
27
+ # or if we are stuck before uvicorn starts.
28
+ def handle_signal(signum, frame):
29
+ logging.info(f"Received signal {signum}, initiating shutdown...")
30
+ # We rely on uvicorn to handle the actual exit loop for SIGINT/SIGTERM usually,
31
+ # but having this log confirms propagation.
32
+ # If uvicorn is running, it should catch this first.
33
+ # If not, we exit manually.
34
+ sys.exit(0)
35
+
36
+ # Note: Uvicorn overwrites SIGINT/SIGTERM handlers by default.
37
+ # relying on lifespan is the standard "Uvicorn way".
38
+
39
+ app = FastAPI(title="Monoco PTY Service", lifespan=lifespan)
40
+ app.include_router(pty_router)
41
+
42
+ # If cwd is provided, we might want to set it as current process CWD
43
+ # so that new sessions default to it.
44
+ if cwd and cwd.exists():
45
+ import os
46
+ os.chdir(cwd)
47
+ logging.info(f"PTY Service Root: {cwd}")
48
+
49
+ logging.info(f"Starting Monoco PTY Service on ws://{host}:{port}")
50
+ try:
51
+ uvicorn.run(app, host=host, port=port)
52
+ except KeyboardInterrupt:
53
+ pass
54
+ finally:
55
+ # Final safety net
56
+ pty_manager.close_all_sessions()
monoco/main.py CHANGED
@@ -11,8 +11,24 @@ app = typer.Typer(
11
11
  )
12
12
 
13
13
 
14
+ def version_callback(value: bool):
15
+ if value:
16
+ import importlib.metadata
17
+ try:
18
+ version = importlib.metadata.version("monoco-toolkit")
19
+ except importlib.metadata.PackageNotFoundError:
20
+ version = "unknown"
21
+ print(f"Monoco Toolkit v{version}")
22
+ raise typer.Exit()
23
+
24
+
14
25
  @app.callback()
15
- def main(ctx: typer.Context):
26
+ def main(
27
+ ctx: typer.Context,
28
+ version: Optional[bool] = typer.Option(
29
+ None, "--version", "-v", help="Show version and exit", callback=version_callback, is_eager=True
30
+ ),
31
+ ):
16
32
  """
17
33
  Monoco Toolkit - The sensory and motor system for Monoco Agents.
18
34
  """
@@ -42,8 +58,14 @@ def info():
42
58
 
43
59
  mode = "Agent (JSON)" if os.getenv("AGENT_FLAG") == "true" else "Human (Rich)"
44
60
 
61
+ import importlib.metadata
62
+ try:
63
+ version = importlib.metadata.version("monoco-toolkit")
64
+ except importlib.metadata.PackageNotFoundError:
65
+ version = "unknown"
66
+
45
67
  status = Status(
46
- version="0.1.0",
68
+ version=version,
47
69
  mode=mode,
48
70
  root=os.getcwd(),
49
71
  project=f"{settings.project.name} ({settings.project.key})"
@@ -69,5 +91,20 @@ app.add_typer(config_cmd.app, name="config", help="Manage configuration")
69
91
  from monoco.daemon.commands import serve
70
92
  app.command(name="serve")(serve)
71
93
 
94
+ @app.command()
95
+ def pty(
96
+ host: str = "127.0.0.1",
97
+ port: int = 3124,
98
+ cwd: Optional[str] = None
99
+ ):
100
+ """
101
+ Start the Monoco PTY Daemon (WebSocket).
102
+ """
103
+ from monoco.features.pty.server import run_pty_server
104
+ from pathlib import Path
105
+
106
+ path = Path(cwd) if cwd else None
107
+ run_pty_server(host, port, path)
108
+
72
109
  if __name__ == "__main__":
73
110
  app()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: monoco-toolkit
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: Agent Native Toolkit for Monoco - Task Management & Kanban for AI Agents
5
5
  Project-URL: Homepage, https://monoco.io
6
6
  Project-URL: Repository, https://github.com/IndenScale/Monoco
@@ -83,7 +83,7 @@ monoco serve
83
83
  Then, launch the visual mission dashboard from anywhere:
84
84
 
85
85
  ```bash
86
- npx @monoco/kanban
86
+ npx @monoco-io/kanban
87
87
  ```
88
88
 
89
89
  Visit `http://localhost:3123` (or the URL displayed in your terminal) to enter your cockpit.
@@ -1,10 +1,10 @@
1
- monoco/main.py,sha256=XBYDD9kQt_a0wRIZ1vtOshRG8kUugach7CnHoAgS84I,2052
1
+ monoco/main.py,sha256=_16blpwjqnSsmahwj9lJ6Cgm_6OVYw9BsdORepY3zX8,3058
2
2
  monoco/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  monoco/core/config.py,sha256=oiWB4bYkLGNhV9J3nGHdYjnVIux0gi8u9oYEpcvanpA,4768
4
4
  monoco/core/git.py,sha256=Qy5VjCKe0Y1y0rjqYULxT_viS7S4phTljf8hkd9DA8Q,6424
5
5
  monoco/core/output.py,sha256=CK8efvj0Q-pWrcJMdXwbuCyfsykWZ_pen9YWuDsivXQ,3192
6
6
  monoco/core/setup.py,sha256=qbMh6LV5wvXNYwquzxpmf3JZAoNDcwLClnNZ2QXPajs,8406
7
- monoco/core/telemetry.py,sha256=7sW468fTLFEfIGyTAsIagmJeQild07kIwM9M_KrPITM,2841
7
+ monoco/core/telemetry.py,sha256=DZQGOhvpe0XL34RCDaTZEUhmkD4abBTZumZJQlALzpY,2923
8
8
  monoco/core/workspace.py,sha256=pFMC2culomxOF6Q1XqpEHCB8WjEEPBxKObUsOVpIG-E,1306
9
9
  monoco/daemon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  monoco/daemon/app.py,sha256=RdNVjaB1JJUJ3_qCfKzBWyTRoNZV10KsBxoMHG--To8,12819
@@ -19,15 +19,18 @@ monoco/features/i18n/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
19
19
  monoco/features/i18n/commands.py,sha256=M_T8Ddw54p42wsZIowFjBq72mXKLsppzBRcHUByfhls,4816
20
20
  monoco/features/i18n/core.py,sha256=recjfNhJA2xdqFUMIivZ2omQZEzbgXJ1QvhjmvEDj24,5957
21
21
  monoco/features/issue/commands.py,sha256=w89KS19pJfMRzbIMIECQatalvPe4Q5rT8vSPfUnRQik,29448
22
- monoco/features/issue/core.py,sha256=FW3S-qdWw5XTze1X3fuNk8O6IbjJl2wRcrGXQYI02_Q,43483
22
+ monoco/features/issue/core.py,sha256=iX-p7fTQUThKcAYOzlbBHldqG5naJV012cBvIxYIeFA,43778
23
23
  monoco/features/issue/linter.py,sha256=ZMNpp_0ehbzBMYROfPtCr4O4JL8mhdO9L0F3EAi96lE,7657
24
- monoco/features/issue/models.py,sha256=JCipMV2FMdyyvy-KIHtjOJHzcE2Cw_Hi-z1M3XfRQfI,4843
24
+ monoco/features/issue/models.py,sha256=sepGxdgvRFRSc4tyvcUB9oXFBkDyS9_g0bGZEaE8Eq8,4970
25
+ monoco/features/pty/core.py,sha256=eM1EvHQrgExSnatO15pyfhh1VZoz0BBTfNYdXqG8HZ8,5711
26
+ monoco/features/pty/router.py,sha256=7h80EPpOuE7hX5ifkxkzffcLZGecd5X8OmNvOji5ToI,5078
27
+ monoco/features/pty/server.py,sha256=kw2csMZ_R4_Xx6ta2dbznWtgNZLfrWOAkMp8NjlZYBc,1920
25
28
  monoco/features/skills/__init__.py,sha256=L8YNGPWyyFWq5WqNossfeB0AKHJF_omrn1VzJBrRFcM,23
26
29
  monoco/features/skills/core.py,sha256=mpd0Cq-k2MvHRTPq9saFvZgYXUBGJ9pnK5lUmzUfZbY,3418
27
30
  monoco/features/spike/commands.py,sha256=BpwYYIpihmezJACOxgQojsCM7RwXqL0_EB6wCl--Mm8,3947
28
31
  monoco/features/spike/core.py,sha256=FSlO3Lhz49_nyFp68TpxBdkFKksShz5S33Qxh74ZFpY,5150
29
- monoco_toolkit-0.1.1.dist-info/METADATA,sha256=Qc4fBORWFOoAGbGyTZAwmVWFsWJZg2f2tTNDiH_LKng,3740
30
- monoco_toolkit-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
31
- monoco_toolkit-0.1.1.dist-info/entry_points.txt,sha256=iYj7FWYBdtClU15-Du1skqD0s6SFSIhJvxJ29VWp8ng,43
32
- monoco_toolkit-0.1.1.dist-info/licenses/LICENSE,sha256=ACAGGjV6aod4eIlVUTx1q9PZbnZGN5bBwkSs9RHj83s,1071
33
- monoco_toolkit-0.1.1.dist-info/RECORD,,
32
+ monoco_toolkit-0.1.3.dist-info/METADATA,sha256=RGD_jbehEYX32G6R5sbGFPtuat0-TUFfYWOxlWLH_Ak,3743
33
+ monoco_toolkit-0.1.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
34
+ monoco_toolkit-0.1.3.dist-info/entry_points.txt,sha256=iYj7FWYBdtClU15-Du1skqD0s6SFSIhJvxJ29VWp8ng,43
35
+ monoco_toolkit-0.1.3.dist-info/licenses/LICENSE,sha256=ACAGGjV6aod4eIlVUTx1q9PZbnZGN5bBwkSs9RHj83s,1071
36
+ monoco_toolkit-0.1.3.dist-info/RECORD,,