gui-now 1.0.0__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.
gui_now-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stratus 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.
gui_now-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: gui-now
3
+ Version: 1.0.0
4
+ Summary: Python SDK + agent tools for gui.now — instant shareable HTML canvases
5
+ License: MIT
6
+ Project-URL: Homepage, https://gui.now
7
+ Project-URL: Repository, https://github.com/gui-now/python
8
+ Keywords: gui-now,html,canvas,sharing,langchain,llamaindex,crewai,agent
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Provides-Extra: langchain
17
+ Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
18
+ Requires-Dist: pydantic>=2.0; extra == "langchain"
19
+ Provides-Extra: llamaindex
20
+ Requires-Dist: llama-index-core>=0.10.0; extra == "llamaindex"
21
+ Provides-Extra: crewai
22
+ Requires-Dist: crewai>=0.1.0; extra == "crewai"
23
+ Requires-Dist: pydantic>=2.0; extra == "crewai"
24
+ Provides-Extra: all
25
+ Requires-Dist: langchain-core>=0.1.0; extra == "all"
26
+ Requires-Dist: llama-index-core>=0.10.0; extra == "all"
27
+ Requires-Dist: crewai>=0.1.0; extra == "all"
28
+ Requires-Dist: pydantic>=2.0; extra == "all"
29
+ Dynamic: license-file
30
+
31
+ # gui-now
32
+
33
+ Python SDK + agent tools for [GUI](https://gui.now) — instant shareable HTML canvases.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install gui-now
39
+ ```
40
+
41
+ With framework integrations:
42
+
43
+ ```bash
44
+ pip install gui-now[langchain]
45
+ pip install gui-now[llamaindex]
46
+ pip install gui-now[crewai]
47
+ pip install gui-now[all]
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ from guinow import create_canvas
54
+
55
+ url = create_canvas(html="<h1>Hello World</h1>", title="My Canvas")
56
+ print(url) # https://gui.now/abc123
57
+ ```
58
+
59
+ ### Full Client
60
+
61
+ ```python
62
+ from guinow import GuiNowClient
63
+
64
+ client = GuiNowClient(api_key="your-pro-key") # or set GUI_NOW_API_KEY env var
65
+ result = client.create(
66
+ html="<h1>Dashboard</h1>",
67
+ title="Q1 Report",
68
+ expires="7d",
69
+ )
70
+ print(result.url)
71
+ print(result.expires_at)
72
+ ```
73
+
74
+ ### Every input format
75
+
76
+ ```python
77
+ client.create(html="<h1>Hi</h1>") # raw HTML
78
+ client.create(markdown="# Hi\n\nRendered server-side.") # Markdown
79
+ client.create_diagram("graph TD\n A-->B", title="Flow") # Mermaid
80
+ client.create(frames=[ # multi-tab canvas
81
+ {"html": "<h1>Overview</h1>", "label": "Overview"},
82
+ {"html": "<h1>Detail</h1>", "label": "Detail"},
83
+ ])
84
+ ```
85
+
86
+ ### Update and extend
87
+
88
+ `create()` returns an `edit_token`. Keep it — it is the only way to change a
89
+ canvas afterwards. Free tier allows 3 edits per canvas; Pro is unlimited.
90
+
91
+ ```python
92
+ canvas = client.create(html="<h1>Draft</h1>")
93
+
94
+ client.update(canvas.id, canvas.edit_token, html="<h1>Final</h1>")
95
+ client.update(canvas.id, canvas.edit_token, title="Renamed")
96
+ client.update(canvas.id, canvas.edit_token, password=None) # remove protection
97
+
98
+ client.extend(canvas.id) # push expiry to 24h from now
99
+ canvas.open() # open the URL in a browser
100
+ ```
101
+
102
+ Omitting `password` leaves existing protection alone; passing `None` removes it.
103
+
104
+ ## LangChain
105
+
106
+ ```python
107
+ from guinow.langchain import GuiNowTool
108
+
109
+ tool = GuiNowTool()
110
+ # Add to your agent's tools list
111
+ agent = initialize_agent(tools=[tool], ...)
112
+ ```
113
+
114
+ ## LlamaIndex
115
+
116
+ ```python
117
+ from guinow.llamaindex import get_guinow_tool
118
+
119
+ tool = get_guinow_tool()
120
+ agent = ReActAgent.from_tools([tool], ...)
121
+ ```
122
+
123
+ ## CrewAI
124
+
125
+ ```python
126
+ from guinow.crewai import GuiNowTool
127
+
128
+ tool = GuiNowTool()
129
+ agent = Agent(tools=[tool], ...)
130
+ ```
131
+
132
+ ## Environment Variables
133
+
134
+ | Variable | Description |
135
+ |----------|-------------|
136
+ | `GUI_NOW_API_KEY` | Pro API key for higher rate limits and longer expiry |
137
+ | `GUI_NOW_URL` | Override the API base URL (defaults to `https://gui.now`) |
138
+
139
+ `GUI_NEW_API_KEY` and `GUI_NEW_URL` are the pre-rename names and are still read
140
+ as fallbacks, so keys exported before the move to gui.now keep working.
141
+
142
+ ## License
143
+
144
+ MIT
@@ -0,0 +1,114 @@
1
+ # gui-now
2
+
3
+ Python SDK + agent tools for [GUI](https://gui.now) — instant shareable HTML canvases.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install gui-now
9
+ ```
10
+
11
+ With framework integrations:
12
+
13
+ ```bash
14
+ pip install gui-now[langchain]
15
+ pip install gui-now[llamaindex]
16
+ pip install gui-now[crewai]
17
+ pip install gui-now[all]
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```python
23
+ from guinow import create_canvas
24
+
25
+ url = create_canvas(html="<h1>Hello World</h1>", title="My Canvas")
26
+ print(url) # https://gui.now/abc123
27
+ ```
28
+
29
+ ### Full Client
30
+
31
+ ```python
32
+ from guinow import GuiNowClient
33
+
34
+ client = GuiNowClient(api_key="your-pro-key") # or set GUI_NOW_API_KEY env var
35
+ result = client.create(
36
+ html="<h1>Dashboard</h1>",
37
+ title="Q1 Report",
38
+ expires="7d",
39
+ )
40
+ print(result.url)
41
+ print(result.expires_at)
42
+ ```
43
+
44
+ ### Every input format
45
+
46
+ ```python
47
+ client.create(html="<h1>Hi</h1>") # raw HTML
48
+ client.create(markdown="# Hi\n\nRendered server-side.") # Markdown
49
+ client.create_diagram("graph TD\n A-->B", title="Flow") # Mermaid
50
+ client.create(frames=[ # multi-tab canvas
51
+ {"html": "<h1>Overview</h1>", "label": "Overview"},
52
+ {"html": "<h1>Detail</h1>", "label": "Detail"},
53
+ ])
54
+ ```
55
+
56
+ ### Update and extend
57
+
58
+ `create()` returns an `edit_token`. Keep it — it is the only way to change a
59
+ canvas afterwards. Free tier allows 3 edits per canvas; Pro is unlimited.
60
+
61
+ ```python
62
+ canvas = client.create(html="<h1>Draft</h1>")
63
+
64
+ client.update(canvas.id, canvas.edit_token, html="<h1>Final</h1>")
65
+ client.update(canvas.id, canvas.edit_token, title="Renamed")
66
+ client.update(canvas.id, canvas.edit_token, password=None) # remove protection
67
+
68
+ client.extend(canvas.id) # push expiry to 24h from now
69
+ canvas.open() # open the URL in a browser
70
+ ```
71
+
72
+ Omitting `password` leaves existing protection alone; passing `None` removes it.
73
+
74
+ ## LangChain
75
+
76
+ ```python
77
+ from guinow.langchain import GuiNowTool
78
+
79
+ tool = GuiNowTool()
80
+ # Add to your agent's tools list
81
+ agent = initialize_agent(tools=[tool], ...)
82
+ ```
83
+
84
+ ## LlamaIndex
85
+
86
+ ```python
87
+ from guinow.llamaindex import get_guinow_tool
88
+
89
+ tool = get_guinow_tool()
90
+ agent = ReActAgent.from_tools([tool], ...)
91
+ ```
92
+
93
+ ## CrewAI
94
+
95
+ ```python
96
+ from guinow.crewai import GuiNowTool
97
+
98
+ tool = GuiNowTool()
99
+ agent = Agent(tools=[tool], ...)
100
+ ```
101
+
102
+ ## Environment Variables
103
+
104
+ | Variable | Description |
105
+ |----------|-------------|
106
+ | `GUI_NOW_API_KEY` | Pro API key for higher rate limits and longer expiry |
107
+ | `GUI_NOW_URL` | Override the API base URL (defaults to `https://gui.now`) |
108
+
109
+ `GUI_NEW_API_KEY` and `GUI_NEW_URL` are the pre-rename names and are still read
110
+ as fallbacks, so keys exported before the move to gui.now keep working.
111
+
112
+ ## License
113
+
114
+ MIT
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gui-now"
7
+ version = "1.0.0"
8
+ description = "Python SDK + agent tools for gui.now — instant shareable HTML canvases"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.9"
12
+ keywords = ["gui-now", "html", "canvas", "sharing", "langchain", "llamaindex", "crewai", "agent"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ langchain = ["langchain-core>=0.1.0", "pydantic>=2.0"]
22
+ llamaindex = ["llama-index-core>=0.10.0"]
23
+ crewai = ["crewai>=0.1.0", "pydantic>=2.0"]
24
+ all = ["langchain-core>=0.1.0", "llama-index-core>=0.10.0", "crewai>=0.1.0", "pydantic>=2.0"]
25
+
26
+ [project.urls]
27
+ Homepage = "https://gui.now"
28
+ Repository = "https://github.com/gui-now/python"
29
+
30
+ [tool.setuptools.packages.find]
31
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: gui-now
3
+ Version: 1.0.0
4
+ Summary: Python SDK + agent tools for gui.now — instant shareable HTML canvases
5
+ License: MIT
6
+ Project-URL: Homepage, https://gui.now
7
+ Project-URL: Repository, https://github.com/gui-now/python
8
+ Keywords: gui-now,html,canvas,sharing,langchain,llamaindex,crewai,agent
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Provides-Extra: langchain
17
+ Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
18
+ Requires-Dist: pydantic>=2.0; extra == "langchain"
19
+ Provides-Extra: llamaindex
20
+ Requires-Dist: llama-index-core>=0.10.0; extra == "llamaindex"
21
+ Provides-Extra: crewai
22
+ Requires-Dist: crewai>=0.1.0; extra == "crewai"
23
+ Requires-Dist: pydantic>=2.0; extra == "crewai"
24
+ Provides-Extra: all
25
+ Requires-Dist: langchain-core>=0.1.0; extra == "all"
26
+ Requires-Dist: llama-index-core>=0.10.0; extra == "all"
27
+ Requires-Dist: crewai>=0.1.0; extra == "all"
28
+ Requires-Dist: pydantic>=2.0; extra == "all"
29
+ Dynamic: license-file
30
+
31
+ # gui-now
32
+
33
+ Python SDK + agent tools for [GUI](https://gui.now) — instant shareable HTML canvases.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install gui-now
39
+ ```
40
+
41
+ With framework integrations:
42
+
43
+ ```bash
44
+ pip install gui-now[langchain]
45
+ pip install gui-now[llamaindex]
46
+ pip install gui-now[crewai]
47
+ pip install gui-now[all]
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ from guinow import create_canvas
54
+
55
+ url = create_canvas(html="<h1>Hello World</h1>", title="My Canvas")
56
+ print(url) # https://gui.now/abc123
57
+ ```
58
+
59
+ ### Full Client
60
+
61
+ ```python
62
+ from guinow import GuiNowClient
63
+
64
+ client = GuiNowClient(api_key="your-pro-key") # or set GUI_NOW_API_KEY env var
65
+ result = client.create(
66
+ html="<h1>Dashboard</h1>",
67
+ title="Q1 Report",
68
+ expires="7d",
69
+ )
70
+ print(result.url)
71
+ print(result.expires_at)
72
+ ```
73
+
74
+ ### Every input format
75
+
76
+ ```python
77
+ client.create(html="<h1>Hi</h1>") # raw HTML
78
+ client.create(markdown="# Hi\n\nRendered server-side.") # Markdown
79
+ client.create_diagram("graph TD\n A-->B", title="Flow") # Mermaid
80
+ client.create(frames=[ # multi-tab canvas
81
+ {"html": "<h1>Overview</h1>", "label": "Overview"},
82
+ {"html": "<h1>Detail</h1>", "label": "Detail"},
83
+ ])
84
+ ```
85
+
86
+ ### Update and extend
87
+
88
+ `create()` returns an `edit_token`. Keep it — it is the only way to change a
89
+ canvas afterwards. Free tier allows 3 edits per canvas; Pro is unlimited.
90
+
91
+ ```python
92
+ canvas = client.create(html="<h1>Draft</h1>")
93
+
94
+ client.update(canvas.id, canvas.edit_token, html="<h1>Final</h1>")
95
+ client.update(canvas.id, canvas.edit_token, title="Renamed")
96
+ client.update(canvas.id, canvas.edit_token, password=None) # remove protection
97
+
98
+ client.extend(canvas.id) # push expiry to 24h from now
99
+ canvas.open() # open the URL in a browser
100
+ ```
101
+
102
+ Omitting `password` leaves existing protection alone; passing `None` removes it.
103
+
104
+ ## LangChain
105
+
106
+ ```python
107
+ from guinow.langchain import GuiNowTool
108
+
109
+ tool = GuiNowTool()
110
+ # Add to your agent's tools list
111
+ agent = initialize_agent(tools=[tool], ...)
112
+ ```
113
+
114
+ ## LlamaIndex
115
+
116
+ ```python
117
+ from guinow.llamaindex import get_guinow_tool
118
+
119
+ tool = get_guinow_tool()
120
+ agent = ReActAgent.from_tools([tool], ...)
121
+ ```
122
+
123
+ ## CrewAI
124
+
125
+ ```python
126
+ from guinow.crewai import GuiNowTool
127
+
128
+ tool = GuiNowTool()
129
+ agent = Agent(tools=[tool], ...)
130
+ ```
131
+
132
+ ## Environment Variables
133
+
134
+ | Variable | Description |
135
+ |----------|-------------|
136
+ | `GUI_NOW_API_KEY` | Pro API key for higher rate limits and longer expiry |
137
+ | `GUI_NOW_URL` | Override the API base URL (defaults to `https://gui.now`) |
138
+
139
+ `GUI_NEW_API_KEY` and `GUI_NEW_URL` are the pre-rename names and are still read
140
+ as fallbacks, so keys exported before the move to gui.now keep working.
141
+
142
+ ## License
143
+
144
+ MIT
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/gui_now.egg-info/PKG-INFO
5
+ src/gui_now.egg-info/SOURCES.txt
6
+ src/gui_now.egg-info/dependency_links.txt
7
+ src/gui_now.egg-info/requires.txt
8
+ src/gui_now.egg-info/top_level.txt
9
+ src/guinow/__init__.py
10
+ src/guinow/client.py
11
+ src/guinow/crewai.py
12
+ src/guinow/langchain.py
13
+ src/guinow/llamaindex.py
@@ -0,0 +1,17 @@
1
+
2
+ [all]
3
+ langchain-core>=0.1.0
4
+ llama-index-core>=0.10.0
5
+ crewai>=0.1.0
6
+ pydantic>=2.0
7
+
8
+ [crewai]
9
+ crewai>=0.1.0
10
+ pydantic>=2.0
11
+
12
+ [langchain]
13
+ langchain-core>=0.1.0
14
+ pydantic>=2.0
15
+
16
+ [llamaindex]
17
+ llama-index-core>=0.10.0
@@ -0,0 +1 @@
1
+ guinow
@@ -0,0 +1,26 @@
1
+ """gui.now Python SDK — create shareable HTML canvases."""
2
+
3
+ from guinow.client import (
4
+ CanvasResult,
5
+ GuiNowClient,
6
+ GuiNowError,
7
+ RateLimitError,
8
+ create,
9
+ create_canvas,
10
+ create_diagram,
11
+ extend,
12
+ update,
13
+ )
14
+
15
+ __all__ = [
16
+ "CanvasResult",
17
+ "GuiNowClient",
18
+ "GuiNowError",
19
+ "RateLimitError",
20
+ "create",
21
+ "create_canvas",
22
+ "create_diagram",
23
+ "extend",
24
+ "update",
25
+ ]
26
+ __version__ = "1.0.0"
@@ -0,0 +1,261 @@
1
+ """Core client for the gui.now API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import urllib.error
8
+ import urllib.request
9
+ from dataclasses import dataclass
10
+ from typing import Any, Optional
11
+
12
+
13
+ DEFAULT_BASE_URL = "https://gui.now"
14
+
15
+
16
+ def _default_base_url() -> str:
17
+ # GUI_NEW_URL is the pre-rename name; still honoured so existing setups
18
+ # keep working without the user having to re-export anything.
19
+ return (
20
+ os.environ.get("GUI_NOW_URL")
21
+ or os.environ.get("GUI_NEW_URL")
22
+ or DEFAULT_BASE_URL
23
+ )
24
+
25
+
26
+ def _default_api_key() -> Optional[str]:
27
+ # GUI_NEW_API_KEY is the pre-rename name; see above.
28
+ return os.environ.get("GUI_NOW_API_KEY") or os.environ.get("GUI_NEW_API_KEY")
29
+
30
+
31
+ @dataclass
32
+ class CanvasResult:
33
+ id: str
34
+ url: str
35
+ edit_token: str
36
+ expires_at: str
37
+ pro: bool
38
+ format: str
39
+ password_protected: bool = False
40
+
41
+ def open(self) -> None:
42
+ """Open the canvas URL in the default browser."""
43
+ import webbrowser
44
+
45
+ webbrowser.open(self.url)
46
+
47
+
48
+ class GuiNowError(Exception):
49
+ pass
50
+
51
+
52
+ class RateLimitError(GuiNowError):
53
+ def __init__(self, retry_after: str = "3600"):
54
+ self.retry_after = retry_after
55
+ super().__init__(f"Rate limited. Retry after {retry_after} seconds.")
56
+
57
+
58
+ class _Unset:
59
+ """Sentinel so update() can tell 'omitted' from an explicit None."""
60
+
61
+ def __repr__(self) -> str: # pragma: no cover - debugging aid
62
+ return "<unset>"
63
+
64
+
65
+ _UNSET = _Unset()
66
+
67
+
68
+ class GuiNowClient:
69
+ """Client for the gui.now API."""
70
+
71
+ def __init__(
72
+ self,
73
+ api_key: Optional[str] = None,
74
+ base_url: Optional[str] = None,
75
+ ):
76
+ self.api_key = api_key or _default_api_key()
77
+ self.base_url = (base_url or _default_base_url()).rstrip("/")
78
+
79
+ # ---- create -----------------------------------------------------------
80
+
81
+ def create(
82
+ self,
83
+ *,
84
+ html: Optional[str] = None,
85
+ markdown: Optional[str] = None,
86
+ mermaid: Optional[str] = None,
87
+ frames: Optional[list] = None,
88
+ title: Optional[str] = None,
89
+ theme: Optional[str] = None,
90
+ expires: Optional[str] = None,
91
+ password: Optional[str] = None,
92
+ ) -> CanvasResult:
93
+ """Create a canvas.
94
+
95
+ Exactly one content argument is needed:
96
+ html: raw HTML.
97
+ markdown: Markdown, rendered server-side.
98
+ mermaid: a Mermaid diagram, rendered pannable and zoomable.
99
+ frames: multi-tab views, [{"html": ..., "label": ...}, ...].
100
+
101
+ Args:
102
+ title: display name for the toolbar and OG meta.
103
+ theme: "dark" (default) or "light".
104
+ expires: Pro only: "1h", "24h", "7d", "14d", "30d".
105
+ password: Pro only: password-protect the canvas.
106
+ """
107
+ body: dict[str, Any] = {}
108
+ if html is not None:
109
+ body["html"] = html
110
+ if markdown is not None:
111
+ body["markdown"] = markdown
112
+ if mermaid is not None:
113
+ body["mermaid"] = mermaid
114
+ if frames is not None:
115
+ body["frames"] = frames
116
+ if title is not None:
117
+ body["title"] = title
118
+ if theme is not None:
119
+ body["theme"] = theme
120
+ if expires is not None:
121
+ body["expires"] = expires
122
+ if password is not None:
123
+ body["password"] = password
124
+
125
+ if not any(
126
+ body.get(k) for k in ("html", "markdown", "mermaid", "frames")
127
+ ):
128
+ raise GuiNowError(
129
+ "One of html, markdown, mermaid or frames is required"
130
+ )
131
+
132
+ return _parse(self._request("POST", "/api/canvas", body))
133
+
134
+ def create_diagram(
135
+ self,
136
+ mermaid: str,
137
+ *,
138
+ title: Optional[str] = None,
139
+ theme: Optional[str] = None,
140
+ ) -> CanvasResult:
141
+ """Create a Mermaid diagram canvas."""
142
+ return self.create(mermaid=mermaid, title=title, theme=theme)
143
+
144
+ # ---- update -----------------------------------------------------------
145
+
146
+ def update(
147
+ self,
148
+ canvas_id: str,
149
+ edit_token: str,
150
+ *,
151
+ html: Optional[str] = None,
152
+ title: Optional[str] = None,
153
+ frames: Optional[list] = None,
154
+ password: Any = _UNSET,
155
+ ) -> dict:
156
+ """Replace a canvas's content. Free tier allows 3 edits; Pro unlimited.
157
+
158
+ Pass password=None to remove password protection, or omit it to leave
159
+ whatever protection the canvas already has untouched.
160
+ """
161
+ body: dict[str, Any] = {}
162
+ if html is not None:
163
+ body["html"] = html
164
+ if title is not None:
165
+ body["title"] = title
166
+ if frames is not None:
167
+ body["frames"] = frames
168
+ if password is not _UNSET:
169
+ body["password"] = password
170
+
171
+ if not body:
172
+ raise GuiNowError("update needs at least one field to change")
173
+
174
+ return self._request(
175
+ "PUT",
176
+ f"/api/canvas/{canvas_id}",
177
+ body,
178
+ headers={"Authorization": f"Bearer {edit_token}"},
179
+ )
180
+
181
+ def extend(self, canvas_id: str) -> dict:
182
+ """Extend the canvas expiry to 24 hours from now."""
183
+ return self._request("POST", f"/api/canvas/{canvas_id}/extend", {})
184
+
185
+ # ---- transport --------------------------------------------------------
186
+
187
+ def _request(
188
+ self,
189
+ method: str,
190
+ path: str,
191
+ body: dict,
192
+ headers: Optional[dict] = None,
193
+ ) -> dict:
194
+ req_headers = {"Content-Type": "application/json"}
195
+ if self.api_key:
196
+ req_headers["x-api-key"] = self.api_key
197
+ if headers:
198
+ req_headers.update(headers)
199
+
200
+ req = urllib.request.Request(
201
+ f"{self.base_url}{path}",
202
+ data=json.dumps(body).encode("utf-8"),
203
+ headers=req_headers,
204
+ method=method,
205
+ )
206
+
207
+ try:
208
+ with urllib.request.urlopen(req) as resp:
209
+ return json.loads(resp.read().decode("utf-8"))
210
+ except urllib.error.HTTPError as e:
211
+ if e.code == 429:
212
+ raise RateLimitError(e.headers.get("Retry-After", "3600")) from e
213
+ detail = e.read().decode("utf-8", errors="replace")
214
+ try:
215
+ detail = json.loads(detail).get("error", detail)
216
+ except json.JSONDecodeError:
217
+ pass
218
+ raise GuiNowError(f"API error ({e.code}): {detail}") from e
219
+
220
+
221
+ def _parse(d: dict) -> CanvasResult:
222
+ return CanvasResult(
223
+ id=d["id"],
224
+ url=d["url"],
225
+ edit_token=d["edit_token"],
226
+ expires_at=d["expires_at"],
227
+ pro=d.get("pro", False),
228
+ format=d.get("format", "html"),
229
+ password_protected=d.get("password_protected", False),
230
+ )
231
+
232
+
233
+ # ---- module-level conveniences using a default client ---------------------
234
+
235
+
236
+ def create(**kwargs) -> CanvasResult:
237
+ return GuiNowClient().create(**kwargs)
238
+
239
+
240
+ def create_diagram(mermaid: str, **kwargs) -> CanvasResult:
241
+ return GuiNowClient().create_diagram(mermaid, **kwargs)
242
+
243
+
244
+ def update(canvas_id: str, edit_token: str, **kwargs) -> dict:
245
+ return GuiNowClient().update(canvas_id, edit_token, **kwargs)
246
+
247
+
248
+ def extend(canvas_id: str) -> dict:
249
+ return GuiNowClient().extend(canvas_id)
250
+
251
+
252
+ def create_canvas(
253
+ html: Optional[str] = None,
254
+ markdown: Optional[str] = None,
255
+ title: Optional[str] = None,
256
+ expires: Optional[str] = None,
257
+ ) -> str:
258
+ """Quick helper — create a canvas and return just the URL."""
259
+ return GuiNowClient().create(
260
+ html=html, markdown=markdown, title=title, expires=expires
261
+ ).url
@@ -0,0 +1,47 @@
1
+ """CrewAI tool for gui.now."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional, Type
6
+
7
+ try:
8
+ from crewai.tools import BaseTool
9
+ from pydantic import BaseModel, Field
10
+ except ImportError:
11
+ raise ImportError(
12
+ "crewai and pydantic are required. "
13
+ "Install with: pip install gui-now[crewai]"
14
+ )
15
+
16
+ from guinow.client import GuiNowClient
17
+
18
+
19
+ class CreateCanvasInput(BaseModel):
20
+ html: Optional[str] = Field(None, description="Raw HTML content for the canvas")
21
+ markdown: Optional[str] = Field(None, description="Markdown content (rendered server-side)")
22
+ title: Optional[str] = Field(None, description="Optional canvas title")
23
+ expires: Optional[str] = Field(None, description="Expiry: 1h, 24h, 7d, 14d, 30d")
24
+
25
+
26
+ class GuiNowTool(BaseTool):
27
+ """CrewAI tool to create shareable HTML canvases on gui.now."""
28
+
29
+ name: str = "create_gui_canvas"
30
+ description: str = (
31
+ "Create a shareable HTML canvas on gui.now. "
32
+ "Send HTML or Markdown content and get a live URL anyone can view. "
33
+ "Use for dashboards, reports, previews, or any visual output."
34
+ )
35
+ args_schema: Type[BaseModel] = CreateCanvasInput
36
+ api_key: Optional[str] = None
37
+
38
+ def _run(
39
+ self,
40
+ html: Optional[str] = None,
41
+ markdown: Optional[str] = None,
42
+ title: Optional[str] = None,
43
+ expires: Optional[str] = None,
44
+ ) -> str:
45
+ client = GuiNowClient(api_key=self.api_key)
46
+ result = client.create(html=html, markdown=markdown, title=title, expires=expires)
47
+ return f"Canvas created: {result.url} (expires {result.expires_at})"
@@ -0,0 +1,47 @@
1
+ """LangChain tool for gui.now."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional, Type
6
+
7
+ try:
8
+ from langchain_core.tools import BaseTool
9
+ from pydantic import BaseModel, Field
10
+ except ImportError:
11
+ raise ImportError(
12
+ "langchain_core and pydantic are required. "
13
+ "Install with: pip install gui-now[langchain]"
14
+ )
15
+
16
+ from guinow.client import GuiNowClient
17
+
18
+
19
+ class CreateCanvasInput(BaseModel):
20
+ html: Optional[str] = Field(None, description="Raw HTML content for the canvas")
21
+ markdown: Optional[str] = Field(None, description="Markdown content (rendered server-side)")
22
+ title: Optional[str] = Field(None, description="Optional canvas title")
23
+ expires: Optional[str] = Field(None, description="Expiry: 1h, 24h, 7d, 14d, 30d")
24
+
25
+
26
+ class GuiNowTool(BaseTool):
27
+ """LangChain tool to create shareable HTML canvases on gui.now."""
28
+
29
+ name: str = "create_gui_canvas"
30
+ description: str = (
31
+ "Create a shareable HTML canvas on gui.now. "
32
+ "Send HTML or Markdown content and get a live URL anyone can view. "
33
+ "Use for dashboards, reports, previews, or any visual output."
34
+ )
35
+ args_schema: Type[BaseModel] = CreateCanvasInput
36
+ api_key: Optional[str] = None
37
+
38
+ def _run(
39
+ self,
40
+ html: Optional[str] = None,
41
+ markdown: Optional[str] = None,
42
+ title: Optional[str] = None,
43
+ expires: Optional[str] = None,
44
+ ) -> str:
45
+ client = GuiNowClient(api_key=self.api_key)
46
+ result = client.create(html=html, markdown=markdown, title=title, expires=expires)
47
+ return f"Canvas created: {result.url} (expires {result.expires_at})"
@@ -0,0 +1,54 @@
1
+ """LlamaIndex tool for gui.now."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ try:
8
+ from llama_index.core.tools import FunctionTool
9
+ except ImportError:
10
+ raise ImportError(
11
+ "llama_index is required. "
12
+ "Install with: pip install gui-now[llamaindex]"
13
+ )
14
+
15
+ from guinow.client import GuiNowClient
16
+
17
+
18
+ def _create_canvas(
19
+ html: str = "",
20
+ markdown: str = "",
21
+ title: str = "",
22
+ expires: str = "",
23
+ ) -> str:
24
+ """Create a shareable HTML canvas on gui.now.
25
+
26
+ Args:
27
+ html: Raw HTML content for the canvas
28
+ markdown: Markdown content (rendered server-side). Use instead of html for text content.
29
+ title: Optional canvas title
30
+ expires: Expiry duration: 1h, 24h, 7d, 14d, 30d
31
+
32
+ Returns:
33
+ The shareable canvas URL
34
+ """
35
+ client = GuiNowClient()
36
+ result = client.create(
37
+ html=html or None,
38
+ markdown=markdown or None,
39
+ title=title or None,
40
+ expires=expires or None,
41
+ )
42
+ return f"Canvas created: {result.url} (expires {result.expires_at})"
43
+
44
+
45
+ def get_guinow_tool() -> FunctionTool:
46
+ """Get a LlamaIndex FunctionTool for gui.now canvas creation."""
47
+ return FunctionTool.from_defaults(
48
+ fn=_create_canvas,
49
+ name="create_gui_canvas",
50
+ description=(
51
+ "Create a shareable HTML canvas on gui.now. "
52
+ "Send HTML or Markdown content and get a live URL anyone can view."
53
+ ),
54
+ )