zettcode-server 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.
- zettcode_server/__init__.py +33 -0
- zettcode_server/app.py +580 -0
- zettcode_server/cli.py +114 -0
- zettcode_server/runner.py +472 -0
- zettcode_server/schemas.py +555 -0
- zettcode_server/serialize.py +292 -0
- zettcode_server/settings.py +58 -0
- zettcode_server-0.1.0.dist-info/METADATA +66 -0
- zettcode_server-0.1.0.dist-info/RECORD +11 -0
- zettcode_server-0.1.0.dist-info/WHEEL +4 -0
- zettcode_server-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""The SDK's values as the HTTP API's own: one place that decides what crosses the wire.
|
|
2
|
+
|
|
3
|
+
A conversion lives here rather than in the endpoints so the format is written down once: an SDK field that is not
|
|
4
|
+
meant for a client is left out here, a field the API documents is produced here, and the event stream and the final
|
|
5
|
+
result are shaped by the same rules.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from .schemas import (
|
|
13
|
+
CompactionOut,
|
|
14
|
+
ContextOut,
|
|
15
|
+
ContextShareOut,
|
|
16
|
+
CurrentSessionOut,
|
|
17
|
+
DayTotalsOut,
|
|
18
|
+
MentionOut,
|
|
19
|
+
MessageOut,
|
|
20
|
+
ModelCheckOut,
|
|
21
|
+
ModelOut,
|
|
22
|
+
ModelTotalsOut,
|
|
23
|
+
ResultOut,
|
|
24
|
+
SessionOut,
|
|
25
|
+
SessionStatsOut,
|
|
26
|
+
SessionTotalsOut,
|
|
27
|
+
ToolOut,
|
|
28
|
+
TotalsOut,
|
|
29
|
+
UsageOut,
|
|
30
|
+
WorkspaceUsageOut,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING: # pragma: no cover - annotations only
|
|
34
|
+
from zettcode.sdk.events import AnyEvent
|
|
35
|
+
from zettcode.sdk.session import Session
|
|
36
|
+
from zettcode.sdk.types import (
|
|
37
|
+
Compaction,
|
|
38
|
+
ContextReport,
|
|
39
|
+
MentionInfo,
|
|
40
|
+
Message,
|
|
41
|
+
ModelCheck,
|
|
42
|
+
ModelInfo,
|
|
43
|
+
RunResult,
|
|
44
|
+
SessionInfo,
|
|
45
|
+
SessionStatsInfo,
|
|
46
|
+
ToolRecord,
|
|
47
|
+
Totals,
|
|
48
|
+
Usage,
|
|
49
|
+
WorkspaceUsage,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def totals_out(totals: Totals) -> TotalsOut:
|
|
54
|
+
"""Return a tally as the API counts it, with the totals the terminal shows worked out."""
|
|
55
|
+
return TotalsOut(
|
|
56
|
+
requests=totals.requests,
|
|
57
|
+
input_tokens=totals.input_tokens,
|
|
58
|
+
output_tokens=totals.output_tokens,
|
|
59
|
+
cache_read_tokens=totals.cache_read_tokens,
|
|
60
|
+
reasoning_tokens=totals.reasoning_tokens,
|
|
61
|
+
total_tokens=totals.total_tokens,
|
|
62
|
+
cache_share=totals.cache_share,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def ledger_out(usage: WorkspaceUsage) -> WorkspaceUsageOut:
|
|
67
|
+
"""Return the workspace's usage the way the ``/usage`` page lays it out."""
|
|
68
|
+
return WorkspaceUsageOut(
|
|
69
|
+
sessions=[
|
|
70
|
+
SessionTotalsOut(
|
|
71
|
+
id=session.id,
|
|
72
|
+
title=session.title,
|
|
73
|
+
started=session.started,
|
|
74
|
+
updated=session.updated,
|
|
75
|
+
tokens=totals_out(session.tokens),
|
|
76
|
+
models=list(session.models),
|
|
77
|
+
entries=session.entries,
|
|
78
|
+
)
|
|
79
|
+
for session in usage.sessions
|
|
80
|
+
],
|
|
81
|
+
models=[ModelTotalsOut(model=item.model, tokens=totals_out(item.tokens)) for item in usage.models],
|
|
82
|
+
days=[DayTotalsOut(day=item.day, tokens=totals_out(item.tokens)) for item in usage.days],
|
|
83
|
+
total=totals_out(usage.total),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def stats_out(stats: SessionStatsInfo) -> SessionStatsOut:
|
|
88
|
+
"""Return one session's stored totals, with the share the resume panel colours."""
|
|
89
|
+
return SessionStatsOut(
|
|
90
|
+
id=stats.id,
|
|
91
|
+
entries=stats.entries,
|
|
92
|
+
requests=stats.requests,
|
|
93
|
+
input_tokens=stats.input_tokens,
|
|
94
|
+
output_tokens=stats.output_tokens,
|
|
95
|
+
cache_read_tokens=stats.cache_read_tokens,
|
|
96
|
+
reasoning_tokens=stats.reasoning_tokens,
|
|
97
|
+
total_tokens=stats.total_tokens,
|
|
98
|
+
cache_share=stats.cache_share,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def mention_out(mention: MentionInfo) -> MentionOut:
|
|
103
|
+
"""Return one ``@`` resource as a menu row."""
|
|
104
|
+
return MentionOut(token=mention.token, kind=mention.kind, name=mention.name, description=mention.description)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def check_out(check: ModelCheck) -> ModelCheckOut:
|
|
108
|
+
"""Return one model's doctor result."""
|
|
109
|
+
return ModelCheckOut(
|
|
110
|
+
model=check.model,
|
|
111
|
+
model_id=check.model_id,
|
|
112
|
+
ok=check.ok,
|
|
113
|
+
seconds=check.seconds,
|
|
114
|
+
detail=check.detail,
|
|
115
|
+
input_tokens=check.input_tokens,
|
|
116
|
+
output_tokens=check.output_tokens,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def usage_out(usage: Usage) -> UsageOut:
|
|
121
|
+
"""Return the token counts of a session or a run."""
|
|
122
|
+
return UsageOut(
|
|
123
|
+
input_tokens=usage.input_tokens,
|
|
124
|
+
output_tokens=usage.output_tokens,
|
|
125
|
+
cache_read_tokens=usage.cache_read_tokens,
|
|
126
|
+
cache_write_tokens=usage.cache_write_tokens,
|
|
127
|
+
reasoning_tokens=usage.reasoning_tokens,
|
|
128
|
+
seconds=usage.seconds,
|
|
129
|
+
requests=usage.requests,
|
|
130
|
+
context_tokens=usage.context_tokens,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def model_out(model: ModelInfo) -> ModelOut:
|
|
135
|
+
"""Return one configured model, without its token or its address."""
|
|
136
|
+
return ModelOut(
|
|
137
|
+
name=model.name,
|
|
138
|
+
model=model.model,
|
|
139
|
+
context_window=model.context_window,
|
|
140
|
+
multimodal=model.multimodal,
|
|
141
|
+
efforts=list(model.efforts),
|
|
142
|
+
active=model.active,
|
|
143
|
+
compact_percent=model.compact_percent,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def session_out(info: SessionInfo, current: str | None = None) -> SessionOut:
|
|
148
|
+
"""Return one stored session: when it changed, and whether it is the one a prompt would continue."""
|
|
149
|
+
return SessionOut(
|
|
150
|
+
id=info.id,
|
|
151
|
+
title=info.title,
|
|
152
|
+
created_at=info.created_at,
|
|
153
|
+
updated_at=info.updated_at,
|
|
154
|
+
current=info.id == current,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def current_session_out(session: Session) -> CurrentSessionOut:
|
|
159
|
+
"""Return the conversation the next prompt continues."""
|
|
160
|
+
return CurrentSessionOut(
|
|
161
|
+
id=session.id,
|
|
162
|
+
title=session.title,
|
|
163
|
+
locked=session.locked,
|
|
164
|
+
usage=usage_out(session.usage),
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def tool_out(tool: ToolRecord) -> ToolOut:
|
|
169
|
+
"""Return one tool call and how it ended."""
|
|
170
|
+
return ToolOut(
|
|
171
|
+
call_id=tool.id,
|
|
172
|
+
name=tool.name,
|
|
173
|
+
arguments=dict(tool.arguments),
|
|
174
|
+
output=tool.output,
|
|
175
|
+
ok=tool.ok,
|
|
176
|
+
skipped=tool.skipped,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def message_out(message: Message) -> MessageOut:
|
|
181
|
+
"""Return one message of a stored conversation."""
|
|
182
|
+
return MessageOut(
|
|
183
|
+
role=message.role,
|
|
184
|
+
text=message.text,
|
|
185
|
+
tool_calls=[
|
|
186
|
+
ToolOut(call_id=call.id, name=call.name, arguments=dict(call.arguments), output="", ok=True)
|
|
187
|
+
for call in message.tool_calls
|
|
188
|
+
],
|
|
189
|
+
tool_call_id=message.tool_call_id,
|
|
190
|
+
tool_name=message.tool_name,
|
|
191
|
+
ok=message.ok,
|
|
192
|
+
images=message.images,
|
|
193
|
+
usage=None if message.usage is None else usage_out(message.usage),
|
|
194
|
+
seconds=message.seconds,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def compaction_out(compaction: Compaction | None) -> CompactionOut:
|
|
199
|
+
"""Return what a compaction did, or that it applied nothing."""
|
|
200
|
+
if compaction is None:
|
|
201
|
+
return CompactionOut(applied=False)
|
|
202
|
+
return CompactionOut(
|
|
203
|
+
applied=True,
|
|
204
|
+
summary=compaction.summary,
|
|
205
|
+
compressed=compaction.compressed,
|
|
206
|
+
kept=compaction.kept,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def context_out(report: ContextReport) -> ContextOut:
|
|
211
|
+
"""Return how full the context window is and what is filling it."""
|
|
212
|
+
return ContextOut(
|
|
213
|
+
window=report.window,
|
|
214
|
+
used=report.used,
|
|
215
|
+
measured_by=report.measured_by,
|
|
216
|
+
shares=[ContextShareOut(name=share.name, tokens=share.tokens, items=share.items) for share in report.shares],
|
|
217
|
+
partial=report.partial,
|
|
218
|
+
from_provider=report.from_provider,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def result_out(result: RunResult) -> ResultOut:
|
|
223
|
+
"""Return what a run came to, with its tool calls but not its raw event log."""
|
|
224
|
+
return ResultOut(
|
|
225
|
+
text=result.text,
|
|
226
|
+
session_id=result.session_id,
|
|
227
|
+
usage=usage_out(result.usage),
|
|
228
|
+
tools=[tool_out(tool) for tool in result.tools],
|
|
229
|
+
stop=result.stop,
|
|
230
|
+
stop_message=result.stop_message,
|
|
231
|
+
questions=list(result.questions),
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def event_payload(event: AnyEvent) -> dict[str, Any]:
|
|
236
|
+
"""Return one event of a run as the JSON an SSE frame carries.
|
|
237
|
+
|
|
238
|
+
Every payload has a ``type``; the rest of the fields depend on it, and are documented with the stream. An event
|
|
239
|
+
this server does not know is sent as ``{"type": "event", "name": ...}`` rather than dropped, so a newer SDK
|
|
240
|
+
cannot make a stream look as if nothing happened.
|
|
241
|
+
"""
|
|
242
|
+
from zettcode.sdk import events
|
|
243
|
+
|
|
244
|
+
match event:
|
|
245
|
+
case events.TextDelta(text=text):
|
|
246
|
+
return {"type": "text_delta", "text": text}
|
|
247
|
+
case events.ReasoningDelta(text=text):
|
|
248
|
+
return {"type": "reasoning_delta", "text": text}
|
|
249
|
+
case events.ToolStarted(call_id=call_id, name=name, arguments=arguments):
|
|
250
|
+
return {"type": "tool_started", "call_id": call_id, "name": name, "arguments": dict(arguments)}
|
|
251
|
+
case events.ToolFinished(call_id=call_id, name=name, output=output, ok=ok, skipped=skipped):
|
|
252
|
+
return {
|
|
253
|
+
"type": "tool_finished",
|
|
254
|
+
"call_id": call_id,
|
|
255
|
+
"name": name,
|
|
256
|
+
"output": output,
|
|
257
|
+
"ok": ok,
|
|
258
|
+
"skipped": skipped,
|
|
259
|
+
}
|
|
260
|
+
case events.ApprovalRequested(call_id=call_id, command=command):
|
|
261
|
+
return {"type": "approval_requested", "call_id": call_id, "command": command, "approved": event.approved}
|
|
262
|
+
case events.QuestionAsked(call_id=call_id, question=question, options=options, allow_multiple=allow_multiple):
|
|
263
|
+
return {
|
|
264
|
+
"type": "question_asked",
|
|
265
|
+
"call_id": call_id,
|
|
266
|
+
"question": question,
|
|
267
|
+
"options": list(options),
|
|
268
|
+
"allow_multiple": allow_multiple,
|
|
269
|
+
"answered": event.answered,
|
|
270
|
+
}
|
|
271
|
+
case events.UsageUpdated(usage=usage):
|
|
272
|
+
return {"type": "usage_updated", "usage": usage_out(usage).model_dump()}
|
|
273
|
+
case events.CompactionStarted():
|
|
274
|
+
return {"type": "compaction_started"}
|
|
275
|
+
case events.CompactionFinished(applied=applied, compaction=compaction):
|
|
276
|
+
return {
|
|
277
|
+
"type": "compaction_finished",
|
|
278
|
+
"applied": applied,
|
|
279
|
+
"summary": compaction.summary if compaction is not None else "",
|
|
280
|
+
}
|
|
281
|
+
case events.ModelStopped(reason=reason, message=message):
|
|
282
|
+
return {"type": "model_stopped", "reason": reason, "message": message}
|
|
283
|
+
case events.Steered(text=text):
|
|
284
|
+
return {"type": "steered", "text": text}
|
|
285
|
+
case events.Notice(text=text, level=level):
|
|
286
|
+
return {"type": "notice", "text": text, "level": level}
|
|
287
|
+
case events.Finished(text=text):
|
|
288
|
+
return {"type": "finished", "text": text}
|
|
289
|
+
case events.RawEvent():
|
|
290
|
+
return {"type": "raw"}
|
|
291
|
+
case _:
|
|
292
|
+
return {"type": "event", "name": type(event).__name__}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""What the server was started with: the workspace it serves and the level its runs start at.
|
|
2
|
+
|
|
3
|
+
One process serves one workspace. The SDK keeps the working directory of the process, so a second workspace in the
|
|
4
|
+
same process is a conflict rather than a feature; a second workspace is a second ``zettcode-server``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
#: The permission a run starts at. Shell commands are refused, which is what an HTTP endpoint can safely assume:
|
|
14
|
+
#: nobody is at a terminal to answer for one, and whoever calls it names the level they want.
|
|
15
|
+
DEFAULT_PERMISSION = "deny"
|
|
16
|
+
|
|
17
|
+
#: The interface to bind. Loopback, so starting the server does not put a shell on the network by accident.
|
|
18
|
+
DEFAULT_HOST = "127.0.0.1"
|
|
19
|
+
DEFAULT_PORT = 3290
|
|
20
|
+
|
|
21
|
+
#: The environment variable that supplies the bearer token, when one is wanted.
|
|
22
|
+
TOKEN_VARIABLE = "ZETTCODE_SERVER_TOKEN"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class Settings:
|
|
27
|
+
"""The workspace, the socket, and how a run starts.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
workspace: The directory the agent works in.
|
|
31
|
+
host: The interface to bind.
|
|
32
|
+
port: The port to bind.
|
|
33
|
+
token: The bearer token every endpoint but ``/health`` requires; ``None`` requires none. It defaults to
|
|
34
|
+
``$ZETTCODE_SERVER_TOKEN``, so a token can be kept out of the command line.
|
|
35
|
+
permission: The shell permission level a run starts at.
|
|
36
|
+
model: The model to start on, by name, or ``None`` for the first configured one.
|
|
37
|
+
effort: The reasoning level to start on, or ``None`` for the model's own.
|
|
38
|
+
config_path: A configuration file to read instead of ``~/.zettcode/config.toml``.
|
|
39
|
+
title: The ``Server`` header, so a client can tell which server it is talking to.
|
|
40
|
+
log_level: What uvicorn logs; ``info`` says which requests arrived.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
workspace: Path
|
|
44
|
+
host: str = DEFAULT_HOST
|
|
45
|
+
port: int = DEFAULT_PORT
|
|
46
|
+
token: str | None = field(default_factory=lambda: os.environ.get(TOKEN_VARIABLE) or None)
|
|
47
|
+
permission: str = DEFAULT_PERMISSION
|
|
48
|
+
model: str | None = None
|
|
49
|
+
effort: str | None = None
|
|
50
|
+
config_path: Path | None = None
|
|
51
|
+
title: str = "zettcode-server"
|
|
52
|
+
log_level: str = "info"
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def url(self) -> str:
|
|
56
|
+
"""Return the address the server serves on, as a client writes it."""
|
|
57
|
+
host = f"[{self.host}]" if ":" in self.host else self.host
|
|
58
|
+
return f"http://{host}:{self.port}"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: zettcode-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The ZettCode coding agent behind an HTTP API: prompts, streaming, sessions and models
|
|
5
|
+
Project-URL: Repository, https://github.com/Chang-LeHung/zettcode
|
|
6
|
+
Project-URL: Issues, https://github.com/Chang-LeHung/zettcode/issues
|
|
7
|
+
Author: Chang-LeHung
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: agent,api,coding-agent,http,llm
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Web Environment
|
|
12
|
+
Classifier: Framework :: FastAPI
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: fastapi<1,>=0.115
|
|
24
|
+
Requires-Dist: httpx<1,>=0.27
|
|
25
|
+
Requires-Dist: uvicorn<1,>=0.35
|
|
26
|
+
Requires-Dist: zettcode>=0.1.8
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# zettcode-server
|
|
30
|
+
|
|
31
|
+
The ZettCode coding agent behind an HTTP API, in the same repository as the terminal program and its SDK.
|
|
32
|
+
|
|
33
|
+
One process serves one workspace. Every endpoint drives the released `zettcode` SDK — the same tools, plugins, MCP
|
|
34
|
+
servers, skills, project instructions and session store as the terminal — so a conversation begun here can be reopened
|
|
35
|
+
in `zettcode`, and the other way round. The package holds no agent of its own.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv tool install zettcode-server
|
|
39
|
+
zettcode-server --workspace ~/projects/api
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
zettcode-server 0.1.0 (zettcode 0.1.8) · http://127.0.0.1:3290 · workspace /home/you/projects/api · permission deny · token off
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
It binds loopback on port **3290**, denies shell commands by default, and asks for a bearer token only when one is
|
|
47
|
+
configured (`--token`, `$ZETTCODE_SERVER_TOKEN`).
|
|
48
|
+
|
|
49
|
+
A machine with no model configured is still served: `/health` says `ready: false` and `POST /models` writes the
|
|
50
|
+
`[[models]]` entry (the same one `/config add` writes) and builds the agent, so an editor can set ZettCode up without
|
|
51
|
+
touching a file by hand.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
curl http://127.0.0.1:3290/health
|
|
55
|
+
curl -N -X POST http://127.0.0.1:3290/prompts \
|
|
56
|
+
-H 'content-type: application/json' \
|
|
57
|
+
-d '{"text": "summarize the README", "approvals": {"mode": "deny"}}'
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The stream carries the run's events and ends with its result. A run that needs an answer — a shell command, a
|
|
61
|
+
question — is suspended and answered through `POST /runs/approvals` or `POST /runs/questions`, so a client stays in
|
|
62
|
+
the conversation instead of guessing.
|
|
63
|
+
|
|
64
|
+
- [The guide](https://chang-lehung.github.io/zettcode/server/): install, endpoints, event stream
|
|
65
|
+
- [TypeScript client](../clients/typescript): `npm install zettcode-client`
|
|
66
|
+
- [Python SDK](https://chang-lehung.github.io/zettcode/sdk/): the same agent from Python
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
zettcode_server/__init__.py,sha256=LAmx23s5UvWFRobXmQIlr3c5OFGijrJqmLPrRLGKssg,1175
|
|
2
|
+
zettcode_server/app.py,sha256=wg7hNRzVrqRpI7J6UoShB913qE5cn8_Ybhmn_9z0ISM,28380
|
|
3
|
+
zettcode_server/cli.py,sha256=vfo_m6CVoa_MUu3Uji_unBMnthAqPSKFqFqtzAVPB70,4427
|
|
4
|
+
zettcode_server/runner.py,sha256=UkeckWB-Q-WNNhNL4hWAcc90KDcK7f9yuOJ6I4Pe8fA,20479
|
|
5
|
+
zettcode_server/schemas.py,sha256=hNuh1gHWBQQyBVjjlAkoPnlc14x0Hak5ESJAO1g1WIE,15753
|
|
6
|
+
zettcode_server/serialize.py,sha256=hHhY-qgfZWPrVdk4f0GHx8gte7vasCxIcTo6TG5DyDQ,10217
|
|
7
|
+
zettcode_server/settings.py,sha256=TPyytEOxq0ubyZ0bbs6xtgE8b00DYORBCVlNVUxrqL8,2493
|
|
8
|
+
zettcode_server-0.1.0.dist-info/METADATA,sha256=wAsORUS-EA-_pqXelh8DckbDo7qsqrYxOmkfqH_0iTM,2958
|
|
9
|
+
zettcode_server-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
10
|
+
zettcode_server-0.1.0.dist-info/entry_points.txt,sha256=zxhbq2cCa4MMP3_dQAlP_RVEWpGzjh-dse56yoIiIBo,61
|
|
11
|
+
zettcode_server-0.1.0.dist-info/RECORD,,
|