codini 0.2.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.
codini-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: codini
3
+ Version: 0.2.0
4
+ Summary: Official Python SDK for the Codini AI API
5
+ License: ISC
6
+ Project-URL: Homepage, https://codini.ai
7
+ Project-URL: Repository, https://github.com/codini/codini-python
8
+ Project-URL: Documentation, https://www.npmjs.com/package/codini
9
+ Keywords: codini,ai,sdk,api,flows,agents
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+
22
+ # codini
23
+
24
+ Official Python SDK for the [Codini AI](https://codini.ai) API.
25
+
26
+ Build, manage, and execute AI flows programmatically. Zero dependencies.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install codini
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ```python
37
+ from codini import CodiniClient
38
+
39
+ codini = CodiniClient("your-api-key")
40
+
41
+ # Execute a flow
42
+ result = codini.sync_execute(1, input="Hello")
43
+ print(result["data"]["output"])
44
+
45
+ # Build a flow programmatically
46
+ project = codini.projects.create("My App")
47
+ flow = codini.flows.create("Chat Agent", project["data"]["id"])
48
+
49
+ nodes = codini.flows.add_nodes(flow["data"]["id"], [
50
+ {"type": "onMessageTrigger", "position": {"x": 0, "y": 200}},
51
+ {"type": "create-agent", "position": {"x": 300, "y": 200}, "params": {
52
+ "name": "Assistant", "model": "gpt-4o", "type": "auto",
53
+ "role description": "Helpful assistant"
54
+ }},
55
+ {"type": "replyToChat", "position": {"x": 600, "y": 200}},
56
+ ])
57
+
58
+ trigger, agent, reply = nodes["data"]["nodes"]
59
+ codini.flows.add_edges(flow["data"]["id"], [
60
+ {"source": trigger["id"], "target": agent["id"]},
61
+ {"source": agent["id"], "target": reply["id"]},
62
+ ])
63
+ ```
64
+
65
+ ## API
66
+
67
+ ### Execution
68
+
69
+ ```python
70
+ codini.create_ticket(flow_id=1, use_websocket=True)
71
+ codini.sync_execute(flow_id, input="Hello", variables={"lang": "en"})
72
+ codini.async_execute(flow_id, input="Hello")
73
+ codini.get_run_status(run_id)
74
+ codini.poll_run_status(run_id, interval=2.0, max_attempts=150)
75
+ ```
76
+
77
+ ### Projects
78
+
79
+ ```python
80
+ codini.projects.list()
81
+ codini.projects.create("My App", description="Optional")
82
+ codini.projects.get(project_id)
83
+ codini.projects.update(project_id, name="New Name")
84
+ codini.projects.delete(project_id)
85
+ ```
86
+
87
+ ### Flows
88
+
89
+ ```python
90
+ codini.flows.list() # All flows
91
+ codini.flows.list(project_id=38) # Flows in a project
92
+ codini.flows.get(flow_id)
93
+ codini.flows.create("My Flow", project_id=38)
94
+ codini.flows.update_details(flow_id, name="New Name")
95
+ codini.flows.delete(flow_id)
96
+
97
+ # Programmatic building
98
+ codini.flows.add_nodes(flow_id, [{...}])
99
+ codini.flows.add_edges(flow_id, [{...}])
100
+ codini.flows.build(flow_id, nodes, edges)
101
+
102
+ # Node catalog
103
+ codini.flows.list_node_types()
104
+ codini.flows.get_node_definitions(["create-agent", "chat-memory"])
105
+
106
+ # Full canvas save
107
+ codini.flows.save(flow_id, nodes=[...], edges=[...])
108
+ ```
109
+
110
+ ### Secrets
111
+
112
+ ```python
113
+ codini.secrets.list()
114
+ codini.secrets.create("my_key", "my_value")
115
+ codini.secrets.get("my_key")
116
+ codini.secrets.delete("my_key")
117
+ ```
118
+
119
+ ## Zero Dependencies
120
+
121
+ Uses only Python standard library (`urllib`, `json`). No `requests` needed.
122
+
123
+ ## Requirements
124
+
125
+ Python 3.8+
126
+
127
+ ## License
128
+
129
+ ISC
codini-0.2.0/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # codini
2
+
3
+ Official Python SDK for the [Codini AI](https://codini.ai) API.
4
+
5
+ Build, manage, and execute AI flows programmatically. Zero dependencies.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install codini
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from codini import CodiniClient
17
+
18
+ codini = CodiniClient("your-api-key")
19
+
20
+ # Execute a flow
21
+ result = codini.sync_execute(1, input="Hello")
22
+ print(result["data"]["output"])
23
+
24
+ # Build a flow programmatically
25
+ project = codini.projects.create("My App")
26
+ flow = codini.flows.create("Chat Agent", project["data"]["id"])
27
+
28
+ nodes = codini.flows.add_nodes(flow["data"]["id"], [
29
+ {"type": "onMessageTrigger", "position": {"x": 0, "y": 200}},
30
+ {"type": "create-agent", "position": {"x": 300, "y": 200}, "params": {
31
+ "name": "Assistant", "model": "gpt-4o", "type": "auto",
32
+ "role description": "Helpful assistant"
33
+ }},
34
+ {"type": "replyToChat", "position": {"x": 600, "y": 200}},
35
+ ])
36
+
37
+ trigger, agent, reply = nodes["data"]["nodes"]
38
+ codini.flows.add_edges(flow["data"]["id"], [
39
+ {"source": trigger["id"], "target": agent["id"]},
40
+ {"source": agent["id"], "target": reply["id"]},
41
+ ])
42
+ ```
43
+
44
+ ## API
45
+
46
+ ### Execution
47
+
48
+ ```python
49
+ codini.create_ticket(flow_id=1, use_websocket=True)
50
+ codini.sync_execute(flow_id, input="Hello", variables={"lang": "en"})
51
+ codini.async_execute(flow_id, input="Hello")
52
+ codini.get_run_status(run_id)
53
+ codini.poll_run_status(run_id, interval=2.0, max_attempts=150)
54
+ ```
55
+
56
+ ### Projects
57
+
58
+ ```python
59
+ codini.projects.list()
60
+ codini.projects.create("My App", description="Optional")
61
+ codini.projects.get(project_id)
62
+ codini.projects.update(project_id, name="New Name")
63
+ codini.projects.delete(project_id)
64
+ ```
65
+
66
+ ### Flows
67
+
68
+ ```python
69
+ codini.flows.list() # All flows
70
+ codini.flows.list(project_id=38) # Flows in a project
71
+ codini.flows.get(flow_id)
72
+ codini.flows.create("My Flow", project_id=38)
73
+ codini.flows.update_details(flow_id, name="New Name")
74
+ codini.flows.delete(flow_id)
75
+
76
+ # Programmatic building
77
+ codini.flows.add_nodes(flow_id, [{...}])
78
+ codini.flows.add_edges(flow_id, [{...}])
79
+ codini.flows.build(flow_id, nodes, edges)
80
+
81
+ # Node catalog
82
+ codini.flows.list_node_types()
83
+ codini.flows.get_node_definitions(["create-agent", "chat-memory"])
84
+
85
+ # Full canvas save
86
+ codini.flows.save(flow_id, nodes=[...], edges=[...])
87
+ ```
88
+
89
+ ### Secrets
90
+
91
+ ```python
92
+ codini.secrets.list()
93
+ codini.secrets.create("my_key", "my_value")
94
+ codini.secrets.get("my_key")
95
+ codini.secrets.delete("my_key")
96
+ ```
97
+
98
+ ## Zero Dependencies
99
+
100
+ Uses only Python standard library (`urllib`, `json`). No `requests` needed.
101
+
102
+ ## Requirements
103
+
104
+ Python 3.8+
105
+
106
+ ## License
107
+
108
+ ISC
@@ -0,0 +1,298 @@
1
+ """
2
+ Codini Python SDK
3
+
4
+ Official Python SDK for the Codini AI API.
5
+ Build, manage, and execute AI flows programmatically.
6
+
7
+ pip install codini
8
+
9
+ Usage:
10
+ from codini import CodiniClient
11
+
12
+ codini = CodiniClient("your-api-key")
13
+ result = codini.sync_execute(1, input="Hello")
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import time
19
+ import json
20
+ from typing import Any, Optional
21
+ from urllib.request import Request, urlopen
22
+ from urllib.error import HTTPError
23
+
24
+
25
+ class CodiniAPIError(Exception):
26
+ """Raised when the Codini API returns a non-2xx response."""
27
+
28
+ def __init__(self, status_code: int, message: str):
29
+ self.status_code = status_code
30
+ self.message = message
31
+ super().__init__(f"Codini API error ({status_code}): {message}")
32
+
33
+
34
+ def _request(base_url: str, headers: dict, path: str, method: str = "GET", body: Any = None) -> Any:
35
+ url = f"{base_url}{path}"
36
+ data = json.dumps(body).encode("utf-8") if body is not None else None
37
+
38
+ req = Request(url, data=data, headers=headers, method=method)
39
+
40
+ try:
41
+ with urlopen(req) as resp:
42
+ content = resp.read().decode("utf-8")
43
+ if content:
44
+ return json.loads(content)
45
+ return {}
46
+ except HTTPError as e:
47
+ error_body = e.read().decode("utf-8")
48
+ try:
49
+ error_data = json.loads(error_body)
50
+ msg = error_data.get("message") or error_data.get("error") or e.reason
51
+ except (json.JSONDecodeError, ValueError):
52
+ msg = e.reason
53
+ raise CodiniAPIError(e.code, msg) from None
54
+
55
+
56
+ class ProjectsAPI:
57
+ """Manage projects that contain flows."""
58
+
59
+ def __init__(self, base_url: str, headers: dict):
60
+ self._base_url = base_url
61
+ self._headers = headers
62
+
63
+ def _req(self, path: str, method: str = "GET", body: Any = None) -> Any:
64
+ return _request(self._base_url, self._headers, path, method, body)
65
+
66
+ def list(self) -> dict:
67
+ """List all projects."""
68
+ return self._req("/projects/list/", "GET")
69
+
70
+ def create(self, name: str, description: str = "") -> dict:
71
+ """Create a new project."""
72
+ return self._req("/projects/create/", "POST", {"name": name, "description": description})
73
+
74
+ def get(self, project_id: int) -> dict:
75
+ """Get project details including its flows."""
76
+ return self._req(f"/projects/{project_id}/", "GET")
77
+
78
+ def update(self, project_id: int, name: str = None, description: str = None) -> dict:
79
+ """Update project name and/or description."""
80
+ body = {}
81
+ if name is not None:
82
+ body["name"] = name
83
+ if description is not None:
84
+ body["description"] = description
85
+ return self._req(f"/projects/{project_id}/update/", "POST", body)
86
+
87
+ def delete(self, project_id: int) -> dict:
88
+ """Delete a project and all its flows."""
89
+ return self._req(f"/projects/{project_id}/delete/", "DELETE")
90
+
91
+
92
+ class FlowsAPI:
93
+ """Manage flows — CRUD, programmatic building, and node catalog."""
94
+
95
+ def __init__(self, base_url: str, headers: dict):
96
+ self._base_url = base_url
97
+ self._headers = headers
98
+
99
+ def _req(self, path: str, method: str = "GET", body: Any = None) -> Any:
100
+ return _request(self._base_url, self._headers, path, method, body)
101
+
102
+ def list(self, project_id: int = None) -> dict:
103
+ """List flows. Pass project_id to filter, or omit for all flows."""
104
+ body = {"project_id": project_id} if project_id else None
105
+ return self._req("/flows/list/", "POST", body)
106
+
107
+ def get(self, flow_id: int) -> dict:
108
+ """Get full flow details including nodes and edges."""
109
+ return self._req(f"/flows/{flow_id}/", "POST")
110
+
111
+ def create(self, name: str, project_id: int, description: str = "") -> dict:
112
+ """Create a new empty flow within a project."""
113
+ return self._req("/flows/create/", "POST", {
114
+ "name": name,
115
+ "project_id": project_id,
116
+ "description": description,
117
+ })
118
+
119
+ def update_details(self, flow_id: int, name: str = None, description: str = None) -> dict:
120
+ """Update flow name and/or description."""
121
+ body = {}
122
+ if name is not None:
123
+ body["name"] = name
124
+ if description is not None:
125
+ body["description"] = description
126
+ return self._req(f"/flows/{flow_id}/update-details/", "POST", body)
127
+
128
+ def save(self, flow_id: int, nodes: list, edges: list, name: str = None, description: str = None, ui_state: dict = None) -> dict:
129
+ """Save full flow state — replaces all nodes and edges."""
130
+ body: dict[str, Any] = {"nodes": nodes, "edges": edges}
131
+ if name is not None:
132
+ body["name"] = name
133
+ if description is not None:
134
+ body["description"] = description
135
+ if ui_state is not None:
136
+ body["ui_state"] = ui_state
137
+ return self._req(f"/flows/{flow_id}/update/", "POST", body)
138
+
139
+ def delete(self, flow_id: int) -> dict:
140
+ """Delete a flow."""
141
+ return self._req(f"/flows/{flow_id}/delete/", "DELETE")
142
+
143
+ def add_nodes(self, flow_id: int, nodes: list[dict]) -> dict:
144
+ """
145
+ Programmatically add nodes to a flow. Appends without deleting existing nodes.
146
+
147
+ Each node: {"type": "create-agent", "position": {"x": 0, "y": 0}, "params": {"name": "...", ...}}
148
+
149
+ Returns created node IDs.
150
+ """
151
+ return self._req("/flows/create-api/", "POST", {
152
+ "flow_id": flow_id,
153
+ "nodes": nodes,
154
+ "edges": [],
155
+ })
156
+
157
+ def add_edges(self, flow_id: int, edges: list[dict]) -> dict:
158
+ """
159
+ Programmatically add edges between existing nodes.
160
+
161
+ Each edge: {"source": "node-id", "target": "node-id", "sourceHandle": "...", "targetHandle": "..."}
162
+ """
163
+ return self._req("/flows/create-api/", "POST", {
164
+ "flow_id": flow_id,
165
+ "nodes": [],
166
+ "edges": edges,
167
+ })
168
+
169
+ def build(self, flow_id: int, nodes: list[dict], edges: list[dict]) -> dict:
170
+ """Add both nodes and edges in a single call."""
171
+ return self._req("/flows/create-api/", "POST", {
172
+ "flow_id": flow_id,
173
+ "nodes": nodes,
174
+ "edges": edges,
175
+ })
176
+
177
+ def list_node_types(self) -> list:
178
+ """List all available node types with their categories."""
179
+ return self._req("/flow-nodes/types", "GET")
180
+
181
+ def get_node_definitions(self, node_types: list[str]) -> list:
182
+ """Fetch full definitions for specific node types."""
183
+ return self._req("/flow-nodes/select-types", "POST", {
184
+ "node_types": node_types,
185
+ })
186
+
187
+
188
+ class SecretsAPI:
189
+ """Store and retrieve credentials for use in flow nodes."""
190
+
191
+ def __init__(self, base_url: str, headers: dict):
192
+ self._base_url = base_url
193
+ self._headers = headers
194
+
195
+ def _req(self, path: str, method: str = "GET", body: Any = None) -> Any:
196
+ return _request(self._base_url, self._headers, path, method, body)
197
+
198
+ def list(self) -> dict:
199
+ """List all secrets."""
200
+ return self._req("/secrets/", "POST")
201
+
202
+ def create(self, secret_name: str, secret_data: str) -> dict:
203
+ """Create or update a secret."""
204
+ return self._req("/secrets/create/", "POST", {
205
+ "secret_name": secret_name,
206
+ "secret_data": secret_data,
207
+ })
208
+
209
+ def get(self, secret_name: str) -> dict:
210
+ """Retrieve a secret value by name."""
211
+ return self._req(f"/secrets/{secret_name}/", "GET")
212
+
213
+ def delete(self, secret_name: str) -> dict:
214
+ """Delete a secret by name."""
215
+ return self._req(f"/secrets/{secret_name}/delete/", "DELETE")
216
+
217
+
218
+ class CodiniClient:
219
+ """
220
+ Codini API client — server-side, requires an API key.
221
+
222
+ Usage:
223
+ codini = CodiniClient("your-api-key")
224
+
225
+ # Execute flows
226
+ result = codini.sync_execute(1, input="Hello")
227
+
228
+ # Manage projects and flows
229
+ codini.projects.create("My App")
230
+ codini.flows.build(flow_id, nodes, edges)
231
+
232
+ # Store secrets
233
+ codini.secrets.create("my_key", "my_value")
234
+ """
235
+
236
+ def __init__(self, api_key: str, base_url: str = "http://api.codini.ai.local/api/v1"):
237
+ self._base_url = base_url
238
+ self._headers = {
239
+ "Content-Type": "application/json",
240
+ "Authorization": api_key,
241
+ }
242
+
243
+ self.projects = ProjectsAPI(base_url, self._headers)
244
+ self.flows = FlowsAPI(base_url, self._headers)
245
+ self.secrets = SecretsAPI(base_url, self._headers)
246
+
247
+ def _req(self, path: str, method: str = "GET", body: Any = None) -> Any:
248
+ return _request(self._base_url, self._headers, path, method, body)
249
+
250
+ # ---- Tickets ----
251
+
252
+ def create_ticket(self, **kwargs) -> dict:
253
+ """
254
+ Create an execution ticket for client use.
255
+
256
+ Required: flow_id
257
+ Optional: use_websocket, allowed_flows, rpm, max_runs,
258
+ max_credits_usage, expires_in_seconds, single_use,
259
+ input, variables, webhook_url, webhook_events_subscribed,
260
+ webhook_secret, metadata
261
+ """
262
+ return self._req("/execution-tickets/create/", "POST", kwargs)
263
+
264
+ # ---- Execution ----
265
+
266
+ def sync_execute(self, flow_id: int, input: str = None, variables: dict = None, timeout: int = None) -> dict:
267
+ """Execute a flow synchronously — blocks until complete."""
268
+ body: dict[str, Any] = {}
269
+ if input is not None:
270
+ body["input"] = input
271
+ if variables is not None:
272
+ body["variables"] = variables
273
+ if timeout is not None:
274
+ body["timeout"] = timeout
275
+ return self._req(f"/flows/{flow_id}/execute-sync/", "POST", body)
276
+
277
+ def async_execute(self, flow_id: int, input: str = None, variables: dict = None) -> dict:
278
+ """Execute a flow asynchronously — returns immediately with a run_id."""
279
+ body: dict[str, Any] = {}
280
+ if input is not None:
281
+ body["input"] = input
282
+ if variables is not None:
283
+ body["variables"] = variables
284
+ return self._req(f"/flows/{flow_id}/execute/", "POST", body)
285
+
286
+ def get_run_status(self, run_id: int) -> dict:
287
+ """Get the status of a run."""
288
+ return self._req(f"/runs/{run_id}/", "GET")
289
+
290
+ def poll_run_status(self, run_id: int, interval: float = 2.0, max_attempts: int = 150) -> dict:
291
+ """Poll a run until it completes or fails."""
292
+ for _ in range(max_attempts):
293
+ result = self.get_run_status(run_id)
294
+ status = result.get("data", {}).get("run_status")
295
+ if status in ("COMPLETED", "FAILED"):
296
+ return result
297
+ time.sleep(interval)
298
+ raise TimeoutError(f"Polling timed out after {max_attempts} attempts for run {run_id}")
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: codini
3
+ Version: 0.2.0
4
+ Summary: Official Python SDK for the Codini AI API
5
+ License: ISC
6
+ Project-URL: Homepage, https://codini.ai
7
+ Project-URL: Repository, https://github.com/codini/codini-python
8
+ Project-URL: Documentation, https://www.npmjs.com/package/codini
9
+ Keywords: codini,ai,sdk,api,flows,agents
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+
22
+ # codini
23
+
24
+ Official Python SDK for the [Codini AI](https://codini.ai) API.
25
+
26
+ Build, manage, and execute AI flows programmatically. Zero dependencies.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install codini
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ```python
37
+ from codini import CodiniClient
38
+
39
+ codini = CodiniClient("your-api-key")
40
+
41
+ # Execute a flow
42
+ result = codini.sync_execute(1, input="Hello")
43
+ print(result["data"]["output"])
44
+
45
+ # Build a flow programmatically
46
+ project = codini.projects.create("My App")
47
+ flow = codini.flows.create("Chat Agent", project["data"]["id"])
48
+
49
+ nodes = codini.flows.add_nodes(flow["data"]["id"], [
50
+ {"type": "onMessageTrigger", "position": {"x": 0, "y": 200}},
51
+ {"type": "create-agent", "position": {"x": 300, "y": 200}, "params": {
52
+ "name": "Assistant", "model": "gpt-4o", "type": "auto",
53
+ "role description": "Helpful assistant"
54
+ }},
55
+ {"type": "replyToChat", "position": {"x": 600, "y": 200}},
56
+ ])
57
+
58
+ trigger, agent, reply = nodes["data"]["nodes"]
59
+ codini.flows.add_edges(flow["data"]["id"], [
60
+ {"source": trigger["id"], "target": agent["id"]},
61
+ {"source": agent["id"], "target": reply["id"]},
62
+ ])
63
+ ```
64
+
65
+ ## API
66
+
67
+ ### Execution
68
+
69
+ ```python
70
+ codini.create_ticket(flow_id=1, use_websocket=True)
71
+ codini.sync_execute(flow_id, input="Hello", variables={"lang": "en"})
72
+ codini.async_execute(flow_id, input="Hello")
73
+ codini.get_run_status(run_id)
74
+ codini.poll_run_status(run_id, interval=2.0, max_attempts=150)
75
+ ```
76
+
77
+ ### Projects
78
+
79
+ ```python
80
+ codini.projects.list()
81
+ codini.projects.create("My App", description="Optional")
82
+ codini.projects.get(project_id)
83
+ codini.projects.update(project_id, name="New Name")
84
+ codini.projects.delete(project_id)
85
+ ```
86
+
87
+ ### Flows
88
+
89
+ ```python
90
+ codini.flows.list() # All flows
91
+ codini.flows.list(project_id=38) # Flows in a project
92
+ codini.flows.get(flow_id)
93
+ codini.flows.create("My Flow", project_id=38)
94
+ codini.flows.update_details(flow_id, name="New Name")
95
+ codini.flows.delete(flow_id)
96
+
97
+ # Programmatic building
98
+ codini.flows.add_nodes(flow_id, [{...}])
99
+ codini.flows.add_edges(flow_id, [{...}])
100
+ codini.flows.build(flow_id, nodes, edges)
101
+
102
+ # Node catalog
103
+ codini.flows.list_node_types()
104
+ codini.flows.get_node_definitions(["create-agent", "chat-memory"])
105
+
106
+ # Full canvas save
107
+ codini.flows.save(flow_id, nodes=[...], edges=[...])
108
+ ```
109
+
110
+ ### Secrets
111
+
112
+ ```python
113
+ codini.secrets.list()
114
+ codini.secrets.create("my_key", "my_value")
115
+ codini.secrets.get("my_key")
116
+ codini.secrets.delete("my_key")
117
+ ```
118
+
119
+ ## Zero Dependencies
120
+
121
+ Uses only Python standard library (`urllib`, `json`). No `requests` needed.
122
+
123
+ ## Requirements
124
+
125
+ Python 3.8+
126
+
127
+ ## License
128
+
129
+ ISC
@@ -0,0 +1,7 @@
1
+ README.md
2
+ pyproject.toml
3
+ codini/__init__.py
4
+ codini.egg-info/PKG-INFO
5
+ codini.egg-info/SOURCES.txt
6
+ codini.egg-info/dependency_links.txt
7
+ codini.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ codini
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "codini"
7
+ version = "0.2.0"
8
+ description = "Official Python SDK for the Codini AI API"
9
+ readme = "README.md"
10
+ license = {text = "ISC"}
11
+ requires-python = ">=3.8"
12
+ keywords = ["codini", "ai", "sdk", "api", "flows", "agents"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Intended Audience :: Developers",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.8",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://codini.ai"
27
+ Repository = "https://github.com/codini/codini-python"
28
+ Documentation = "https://www.npmjs.com/package/codini"
codini-0.2.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+