venn-cli 0.1.0__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.
venn_cli/__init__.py ADDED
File without changes
venn_cli/cli.py ADDED
@@ -0,0 +1,265 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import click
9
+ from dotenv import load_dotenv
10
+
11
+ from venn_cli.client import VennClient
12
+ from venn_cli import output
13
+
14
+
15
+ def _load_env() -> None:
16
+ for p in [Path.cwd() / ".env", Path(__file__).resolve().parents[2] / ".env"]:
17
+ if p.exists():
18
+ load_dotenv(p)
19
+ return
20
+ load_dotenv()
21
+
22
+
23
+ def _client(ctx: click.Context) -> VennClient:
24
+ return ctx.obj["client"]
25
+
26
+
27
+ @click.group()
28
+ @click.option("--api-key", envvar="VENN_API_KEY", help="Venn API key (or set VENN_API_KEY)")
29
+ @click.option("--base-url", envvar="VENN_BASE_URL", default=None, help="Override API base URL")
30
+ @click.option("--raw", is_flag=True, help="Output raw JSON instead of formatted tables")
31
+ @click.version_option(package_name="venn-cli")
32
+ @click.pass_context
33
+ def main(ctx: click.Context, api_key: str | None, base_url: str | None, raw: bool) -> None:
34
+ """Venn ToolIQ CLI — discover, inspect, and execute tools."""
35
+ _load_env()
36
+ api_key = api_key or os.environ.get("VENN_API_KEY")
37
+ if not api_key:
38
+ click.echo("Error: VENN_API_KEY not set. Pass --api-key or add it to .env", err=True)
39
+ raise SystemExit(1)
40
+ kwargs = {"api_key": api_key}
41
+ if base_url:
42
+ kwargs["base_url"] = base_url
43
+ ctx.ensure_object(dict)
44
+ ctx.obj["client"] = VennClient(**kwargs)
45
+ ctx.obj["raw"] = raw
46
+
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # venn tools
50
+ # ---------------------------------------------------------------------------
51
+
52
+ @main.group()
53
+ def tools():
54
+ """Discover, inspect, and execute tools."""
55
+
56
+
57
+ @tools.command("list")
58
+ @click.option("--slug", "-s", help="Toolset slug (e.g. 'salesforce', 'gmail')")
59
+ @click.option("--directory-id", "-d", help="Server directory UUID")
60
+ @click.option("--instance-id", "-i", help="Server instance UUID")
61
+ @click.pass_context
62
+ def tools_list(ctx, slug, directory_id, instance_id):
63
+ """List tools for a server."""
64
+ if not any([slug, directory_id, instance_id]):
65
+ click.echo("Provide at least one of: --slug, --directory-id, --instance-id", err=True)
66
+ raise SystemExit(1)
67
+ data = _client(ctx).list_tools(slug=slug, directory_id=directory_id, instance_id=instance_id)
68
+ if ctx.obj["raw"]:
69
+ output.print_json(data)
70
+ else:
71
+ output.print_tool_list(data.get("result", {}))
72
+
73
+
74
+ @tools.command("search")
75
+ @click.argument("query")
76
+ @click.option("--limit", "-n", default=10, type=int, help="Max results")
77
+ @click.option("--offset", default=0, type=int, help="Skip N results")
78
+ @click.option("--min-score", default=0.3, type=float, help="Minimum similarity score")
79
+ @click.option("--refresh", is_flag=True, help="Bypass cache")
80
+ @click.pass_context
81
+ def tools_search(ctx, query, limit, offset, min_score, refresh):
82
+ """Search tools by natural language query."""
83
+ data = _client(ctx).search_tools(query, limit=limit, offset=offset, min_score=min_score, refresh=refresh)
84
+ if ctx.obj["raw"]:
85
+ output.print_json(data)
86
+ else:
87
+ output.print_search_results(data.get("result", {}))
88
+
89
+
90
+ @tools.command("describe")
91
+ @click.option("--server-id", "-s", required=True, help="Server identifier")
92
+ @click.option("--tool-name", "-t", required=True, multiple=True, help="Tool name (repeatable)")
93
+ @click.option("--refresh", is_flag=True, help="Bypass cache")
94
+ @click.pass_context
95
+ def tools_describe(ctx, server_id, tool_name, refresh):
96
+ """Get detailed schema for one or more tools."""
97
+ tools_list = [{"server_id": server_id, "tool_name": t} for t in tool_name]
98
+ data = _client(ctx).describe_tools(tools_list, refresh=refresh)
99
+ if ctx.obj["raw"]:
100
+ output.print_json(data)
101
+ else:
102
+ output.print_tool_details(data.get("result", {}).get("results", []))
103
+
104
+
105
+ @tools.command("execute")
106
+ @click.option("--server-id", "-s", required=True, help="Server identifier")
107
+ @click.option("--tool-name", "-t", required=True, help="Tool name")
108
+ @click.option("--args", "-a", "tool_args", default=None, help="Tool arguments as JSON string")
109
+ @click.option("--confirm", "do_confirm", is_flag=True, help="Auto-obtain confirmation token for write ops")
110
+ @click.pass_context
111
+ def tools_execute(ctx, server_id, tool_name, tool_args, do_confirm):
112
+ """Execute a tool."""
113
+ client = _client(ctx)
114
+ parsed_args = json.loads(tool_args) if tool_args else None
115
+
116
+ confirmation_token = None
117
+ if do_confirm:
118
+ confirm_data = client.confirm_write()
119
+ confirmation_token = confirm_data.get("result", {}).get("confirmation_token")
120
+
121
+ data = client.execute_tool(
122
+ server_id=server_id,
123
+ tool_name=tool_name,
124
+ tool_args=parsed_args,
125
+ confirmed=do_confirm,
126
+ confirmation_token=confirmation_token,
127
+ )
128
+ if ctx.obj["raw"]:
129
+ output.print_json(data)
130
+ else:
131
+ output.print_execute_result(data)
132
+
133
+
134
+ @tools.command("confirm")
135
+ @click.pass_context
136
+ def tools_confirm(ctx):
137
+ """Get a confirmation token for write operations."""
138
+ data = _client(ctx).confirm_write()
139
+ if ctx.obj["raw"]:
140
+ output.print_json(data)
141
+ else:
142
+ output.print_confirm(data.get("result", {}))
143
+
144
+
145
+ # ---------------------------------------------------------------------------
146
+ # venn workflow
147
+ # ---------------------------------------------------------------------------
148
+
149
+ @main.group()
150
+ def workflow():
151
+ """Execute code in a sandboxed environment."""
152
+
153
+
154
+ @workflow.command("run")
155
+ @click.option("--code", "-c", help="Python code to execute")
156
+ @click.option("--file", "-f", "code_file", type=click.Path(exists=True), help="Read code from file")
157
+ @click.option("--timeout", default=180, type=int, help="Max execution time in seconds")
158
+ @click.option("--confirm", "do_confirm", is_flag=True, help="Obtain confirmation for write ops")
159
+ @click.pass_context
160
+ def workflow_run(ctx, code, code_file, timeout, do_confirm):
161
+ """Execute Python code in the Venn sandbox."""
162
+ if code_file:
163
+ code = Path(code_file).read_text()
164
+ if not code:
165
+ click.echo("Provide --code or --file", err=True)
166
+ raise SystemExit(1)
167
+
168
+ client = _client(ctx)
169
+ confirmation_token = None
170
+ if do_confirm:
171
+ confirm_data = client.confirm_write()
172
+ confirmation_token = confirm_data.get("result", {}).get("confirmation_token")
173
+
174
+ data = client.execute_workflow(
175
+ code=code,
176
+ timeout=timeout,
177
+ confirmed=do_confirm,
178
+ confirmation_token=confirmation_token,
179
+ )
180
+ if ctx.obj["raw"]:
181
+ output.print_json(data)
182
+ else:
183
+ output.print_execute_result(data)
184
+
185
+
186
+ # ---------------------------------------------------------------------------
187
+ # venn help
188
+ # ---------------------------------------------------------------------------
189
+
190
+ @main.command("help")
191
+ @click.argument("action", required=False, type=click.Choice(
192
+ ["getting_started", "connector_help", "auth_helper", "list_servers"],
193
+ case_sensitive=False,
194
+ ))
195
+ @click.option("--server-id", "-s", help="Server ID (required for auth_helper)")
196
+ @click.option("--refresh", is_flag=True, help="Bypass cache")
197
+ @click.pass_context
198
+ def help_cmd(ctx, action, server_id, refresh):
199
+ """Get help, server status, or auth URLs."""
200
+ data = _client(ctx).help(action=action, server_id=server_id, refresh=refresh)
201
+ if ctx.obj["raw"]:
202
+ output.print_json(data)
203
+ else:
204
+ output.print_help_result(data.get("result"))
205
+
206
+
207
+ # ---------------------------------------------------------------------------
208
+ # venn skills
209
+ # ---------------------------------------------------------------------------
210
+
211
+ @main.group()
212
+ def skills():
213
+ """Manage executable skills."""
214
+
215
+
216
+ @skills.command("upsert")
217
+ @click.option("--file", "-f", "skill_file", required=True, type=click.Path(exists=True), help="Skill YAML file")
218
+ @click.pass_context
219
+ def skills_upsert(ctx, skill_file):
220
+ """Create or update a skill from a YAML file."""
221
+ content = Path(skill_file).read_text()
222
+ data = _client(ctx).upsert_skill(content)
223
+ if ctx.obj["raw"]:
224
+ output.print_json(data)
225
+ else:
226
+ output.console.print("[green]Skill upserted successfully.[/green]")
227
+ if data.get("result"):
228
+ output.print_json(data["result"])
229
+
230
+
231
+ # ---------------------------------------------------------------------------
232
+ # venn history
233
+ # ---------------------------------------------------------------------------
234
+
235
+ @main.group()
236
+ def history():
237
+ """View catalog and server revision history."""
238
+
239
+
240
+ @history.command("toolset")
241
+ @click.argument("toolset_key")
242
+ @click.option("--limit", "-n", default=50, type=int, help="Max revisions")
243
+ @click.option("--before", "before_version", default=None, type=int, help="Only show versions before this")
244
+ @click.pass_context
245
+ def history_toolset(ctx, toolset_key, limit, before_version):
246
+ """Show revision history for a toolset."""
247
+ data = _client(ctx).toolset_history(toolset_key, limit=limit, before_version=before_version)
248
+ if ctx.obj["raw"]:
249
+ output.print_json(data)
250
+ else:
251
+ output.print_history(data.get("result", {}))
252
+
253
+
254
+ @history.command("server")
255
+ @click.argument("directory_id")
256
+ @click.option("--limit", "-n", default=50, type=int, help="Max revisions")
257
+ @click.option("--before", "before_version", default=None, type=int, help="Only show versions before this")
258
+ @click.pass_context
259
+ def history_server(ctx, directory_id, limit, before_version):
260
+ """Show revision history for a server instance."""
261
+ data = _client(ctx).server_history(directory_id, limit=limit, before_version=before_version)
262
+ if ctx.obj["raw"]:
263
+ output.print_json(data)
264
+ else:
265
+ output.print_history(data.get("result", {}))
venn_cli/client.py ADDED
@@ -0,0 +1,165 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sys
5
+ from typing import Any
6
+
7
+ import httpx
8
+
9
+
10
+ BASE_URL = "https://app.venn.ai/api/tooliq"
11
+
12
+
13
+ class VennClient:
14
+ def __init__(self, api_key: str, base_url: str = BASE_URL, timeout: float = 120):
15
+ self._http = httpx.Client(
16
+ base_url=base_url,
17
+ headers={"Authorization": f"Bearer {api_key}"},
18
+ timeout=timeout,
19
+ )
20
+
21
+ def _get(self, path: str, params: dict | None = None) -> dict:
22
+ resp = self._http.get(path, params=params)
23
+ return self._handle(resp)
24
+
25
+ def _post(self, path: str, payload: dict | None = None) -> dict:
26
+ resp = self._http.post(path, json=payload or {})
27
+ return self._handle(resp)
28
+
29
+ @staticmethod
30
+ def _handle(resp: httpx.Response) -> dict:
31
+ try:
32
+ data = resp.json()
33
+ except Exception:
34
+ resp.raise_for_status()
35
+ return {}
36
+ if not data.get("success", True):
37
+ err = data.get("error", resp.text)
38
+ print(f"API error: {err}", file=sys.stderr)
39
+ raise SystemExit(1)
40
+ resp.raise_for_status()
41
+ return data
42
+
43
+ # -- tools ---------------------------------------------------------------
44
+
45
+ def list_tools(
46
+ self,
47
+ slug: str | None = None,
48
+ directory_id: str | None = None,
49
+ instance_id: str | None = None,
50
+ ) -> dict:
51
+ params: dict[str, Any] = {}
52
+ if slug:
53
+ params["slug"] = slug
54
+ if directory_id:
55
+ params["directory_id"] = directory_id
56
+ if instance_id:
57
+ params["instance_id"] = instance_id
58
+ return self._get("/tools/list", params)
59
+
60
+ def search_tools(
61
+ self,
62
+ query: str,
63
+ limit: int = 10,
64
+ offset: int = 0,
65
+ min_score: float = 0.3,
66
+ refresh: bool = False,
67
+ ) -> dict:
68
+ return self._post("/tools/search", {
69
+ "query": query,
70
+ "limit": limit,
71
+ "offset": offset,
72
+ "min_score": min_score,
73
+ "refresh": refresh,
74
+ })
75
+
76
+ def describe_tools(
77
+ self,
78
+ tools: list[dict[str, str]],
79
+ refresh: bool = False,
80
+ ) -> dict:
81
+ return self._post("/tools/describe", {"tools": tools, "refresh": refresh})
82
+
83
+ def execute_tool(
84
+ self,
85
+ server_id: str,
86
+ tool_name: str,
87
+ tool_args: dict | None = None,
88
+ confirmed: bool = False,
89
+ confirmation_token: str | None = None,
90
+ ) -> dict:
91
+ payload: dict[str, Any] = {
92
+ "server_id": server_id,
93
+ "tool_name": tool_name,
94
+ "confirmed": confirmed,
95
+ }
96
+ if tool_args:
97
+ payload["tool_args"] = tool_args
98
+ if confirmation_token:
99
+ payload["confirmation_token"] = confirmation_token
100
+ return self._post("/tools/execute", payload)
101
+
102
+ def confirm_write(self) -> dict:
103
+ return self._post("/tools/confirm")
104
+
105
+ # -- workflow ------------------------------------------------------------
106
+
107
+ def execute_workflow(
108
+ self,
109
+ code: str,
110
+ timeout: int = 180,
111
+ confirmed: bool = False,
112
+ confirmation_token: str | None = None,
113
+ ) -> dict:
114
+ payload: dict[str, Any] = {
115
+ "code": code,
116
+ "timeout": timeout,
117
+ "confirmed": confirmed,
118
+ }
119
+ if confirmation_token:
120
+ payload["confirmation_token"] = confirmation_token
121
+ return self._post("/tools/execute-workflow", payload)
122
+
123
+ # -- help ----------------------------------------------------------------
124
+
125
+ def help(
126
+ self,
127
+ action: str | None = None,
128
+ server_id: str | None = None,
129
+ refresh: bool = False,
130
+ ) -> dict:
131
+ payload: dict[str, Any] = {"refresh": refresh}
132
+ if action:
133
+ payload["action"] = action
134
+ if server_id:
135
+ payload["server_id"] = server_id
136
+ return self._post("/tools/help", payload)
137
+
138
+ # -- skills --------------------------------------------------------------
139
+
140
+ def upsert_skill(self, content: str) -> dict:
141
+ return self._post("/skills", {"content": content})
142
+
143
+ # -- history -------------------------------------------------------------
144
+
145
+ def toolset_history(
146
+ self,
147
+ toolset_key: str,
148
+ limit: int = 50,
149
+ before_version: int | None = None,
150
+ ) -> dict:
151
+ params: dict[str, Any] = {"limit": limit}
152
+ if before_version is not None:
153
+ params["before_version"] = before_version
154
+ return self._get(f"/toolset/{toolset_key}/history", params)
155
+
156
+ def server_history(
157
+ self,
158
+ directory_id: str,
159
+ limit: int = 50,
160
+ before_version: int | None = None,
161
+ ) -> dict:
162
+ params: dict[str, Any] = {"limit": limit}
163
+ if before_version is not None:
164
+ params["before_version"] = before_version
165
+ return self._get(f"/server/{directory_id}/history", params)
venn_cli/output.py ADDED
@@ -0,0 +1,167 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ from rich.console import Console
7
+ from rich.panel import Panel
8
+ from rich.table import Table
9
+ from rich.syntax import Syntax
10
+ from rich.text import Text
11
+
12
+ console = Console()
13
+ err_console = Console(stderr=True)
14
+
15
+
16
+ def print_json(data: Any) -> None:
17
+ console.print(Syntax(json.dumps(data, indent=2), "json"))
18
+
19
+
20
+ def print_tool_list(result: dict) -> None:
21
+ name = result.get("name", "")
22
+ slug = result.get("slug", "")
23
+ version = result.get("toolset_version", "")
24
+ tools = result.get("tools", [])
25
+
26
+ header = f"{name}"
27
+ if slug:
28
+ header += f" ({slug})"
29
+ if version:
30
+ header += f" v{version}"
31
+
32
+ table = Table(title=header, show_lines=False)
33
+ table.add_column("Tool", style="cyan", no_wrap=True)
34
+ table.add_column("Description")
35
+ table.add_column("CRUD", style="dim", width=8)
36
+ table.add_column("Risk", justify="center", width=5)
37
+ table.add_column("Status", style="dim", width=10)
38
+
39
+ for t in tools:
40
+ risk = str(t.get("risk_level", ""))
41
+ risk_style = "green" if int(risk or 0) <= 1 else "yellow" if int(risk or 0) <= 3 else "red"
42
+ table.add_row(
43
+ t["tool_name"],
44
+ t.get("short_description", ""),
45
+ t.get("crud_operation") or "",
46
+ Text(risk, style=risk_style),
47
+ t.get("status", ""),
48
+ )
49
+ console.print(table)
50
+
51
+
52
+ def print_search_results(result: dict) -> None:
53
+ candidates = result.get("candidates", [])
54
+ total = result.get("total", 0)
55
+
56
+ if not candidates:
57
+ console.print("[dim]No results found.[/dim]")
58
+ return
59
+
60
+ table = Table(title=f"Search Results ({total} total)", show_lines=False)
61
+ table.add_column("#", style="dim", width=4, justify="right")
62
+ table.add_column("Type", width=6)
63
+ table.add_column("Server / Name", style="cyan")
64
+ table.add_column("Tool / Skill")
65
+ table.add_column("Description")
66
+
67
+ for c in candidates:
68
+ rank = str(c.get("rank", ""))
69
+ if c.get("type") == "skill":
70
+ table.add_row(rank, "skill", "", c["name"], c.get("description", ""))
71
+ else:
72
+ connected = "[green]●[/green]" if c.get("server_connected") else "[red]○[/red]"
73
+ table.add_row(
74
+ rank,
75
+ f"tool {connected}",
76
+ c.get("server_name", c.get("server_id", "")),
77
+ c.get("tool_name", ""),
78
+ c.get("short_description", ""),
79
+ )
80
+ console.print(table)
81
+
82
+
83
+ def print_tool_details(results: list[dict]) -> None:
84
+ for t in results:
85
+ title = f"{t.get('server_name', '')} / {t['tool_name']}"
86
+ body_parts = []
87
+ if t.get("description"):
88
+ body_parts.append(t["description"])
89
+ if t.get("extended_description"):
90
+ body_parts.append(f"\n{t['extended_description']}")
91
+ if t.get("write_operation"):
92
+ body_parts.append(f"\n[yellow]Write operation:[/yellow] {t['write_operation']}")
93
+
94
+ console.print(Panel("\n".join(body_parts), title=title, border_style="cyan"))
95
+
96
+ if t.get("inputSchema"):
97
+ console.print(Syntax(json.dumps(t["inputSchema"], indent=2), "json", theme="monokai"))
98
+ console.print()
99
+
100
+
101
+ def print_execute_result(data: dict) -> None:
102
+ result = data.get("result")
103
+ if isinstance(result, (dict, list)):
104
+ print_json(result)
105
+ elif result is not None:
106
+ console.print(result)
107
+
108
+
109
+ def print_confirm(result: dict) -> None:
110
+ token = result.get("confirmation_token", "")
111
+ expires = result.get("expires_in_seconds", "")
112
+ console.print(f"[green]Confirmation token:[/green] {token}")
113
+ console.print(f"[dim]Expires in {expires}s[/dim]")
114
+ console.print(f"\n[dim]{result.get('usage', '')}[/dim]")
115
+
116
+
117
+ def print_history(result: dict) -> None:
118
+ key = result.get("toolset_key") or result.get("directory_id", "")
119
+ revisions = result.get("revisions", [])
120
+
121
+ if not revisions:
122
+ console.print("[dim]No history found.[/dim]")
123
+ return
124
+
125
+ table = Table(title=f"History: {key}", show_lines=False)
126
+ table.add_column("Version", justify="right", width=8)
127
+ table.add_column("Date", width=22)
128
+ table.add_column("Source", style="dim")
129
+ table.add_column("Summary")
130
+
131
+ for r in revisions:
132
+ summary_parts = [f"{k}: {v}" for k, v in r.get("summary", {}).items()]
133
+ table.add_row(
134
+ str(r.get("version", "")),
135
+ r.get("issued_at", ""),
136
+ r.get("source") or "",
137
+ ", ".join(summary_parts),
138
+ )
139
+ console.print(table)
140
+
141
+
142
+ def print_help_result(result: Any) -> None:
143
+ if isinstance(result, list):
144
+ for item in result:
145
+ if isinstance(item, dict):
146
+ _print_help_item(item)
147
+ else:
148
+ console.print(item)
149
+ elif isinstance(result, dict):
150
+ _print_help_item(result)
151
+ else:
152
+ console.print(result)
153
+
154
+
155
+ def _print_help_item(item: dict) -> None:
156
+ name = item.get("name") or item.get("server_name") or ""
157
+ status = item.get("status") or item.get("connected") or ""
158
+ if name:
159
+ indicator = "[green]●[/green]" if status in (True, "connected") else "[red]○[/red]"
160
+ console.print(f" {indicator} [cyan]{name}[/cyan]", end="")
161
+ remaining = {k: v for k, v in item.items() if k not in ("name", "server_name", "status", "connected")}
162
+ if remaining:
163
+ console.print(f" [dim]{remaining}[/dim]")
164
+ else:
165
+ console.print()
166
+ else:
167
+ print_json(item)
@@ -0,0 +1,27 @@
1
+ Metadata-Version: 2.4
2
+ Name: venn-cli
3
+ Version: 0.1.0
4
+ Summary: CLI for the Venn ToolIQ API
5
+ Project-URL: Homepage, https://github.com/moda-labs/venn-cli
6
+ Project-URL: Repository, https://github.com/moda-labs/venn-cli
7
+ Project-URL: Issues, https://github.com/moda-labs/venn-cli/issues
8
+ Author-email: Moda Labs <zach@modalabs.ai>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: click>=8.1
22
+ Requires-Dist: httpx>=0.27
23
+ Requires-Dist: python-dotenv>=1.0
24
+ Requires-Dist: rich>=13.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8.0; extra == 'dev'
27
+ Requires-Dist: respx>=0.22; extra == 'dev'
@@ -0,0 +1,9 @@
1
+ venn_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ venn_cli/cli.py,sha256=V9sZv9G4qflHGB_zJu_mTYlo0J9ITJGznPAuzjGULgk,9596
3
+ venn_cli/client.py,sha256=O-BlREKh9ZxkXXy3_YYyHbi2k3SSrd5NSCRddIBuNgI,4952
4
+ venn_cli/output.py,sha256=EvTlQZLUxsMuNnd_cjvZJsjkPkX6_CscuUeKiLzkUHM,5516
5
+ venn_cli-0.1.0.dist-info/METADATA,sha256=QpcIyub5ngjjzOqWyb2xwjyTatmVeAm0nvK2svMOIT0,1028
6
+ venn_cli-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
7
+ venn_cli-0.1.0.dist-info/entry_points.txt,sha256=r47AOFwFdnb-ptJsHkvb8SQrvUUDmc2hx_LogYtVh8U,43
8
+ venn_cli-0.1.0.dist-info/licenses/LICENSE,sha256=R1js932ljyYmujRLNCDR8NaPdroPHUXZYBrXAqQ179Y,1066
9
+ venn_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ venn = venn_cli.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Moda Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.