agk 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.
agk/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ """app-gui-kit Python サーバ SDK。
2
+
3
+ シミュレーションエージェント系アプリの Flask 定型(SSE ストリーミング、
4
+ 単一フライト、ライフサイクル watchdog、成果物配信、remote-job キュー、
5
+ アップロード、HITL ゲート、起動処理)を提供する。
6
+
7
+ 使い方::
8
+
9
+ from agk import AgkApp
10
+
11
+ def runner(req, ctx):
12
+ ctx.emit.text("hello")
13
+ ctx.emit.done(summary="finished")
14
+
15
+ AgkApp("my-app", runner).run()
16
+ """
17
+
18
+ from .core import AgkApp, AgkContext
19
+ from .events import EventSink
20
+ from .gates import GateHub
21
+
22
+ __all__ = ["AgkApp", "AgkContext", "EventSink", "GateHub", "__version__"]
23
+ __version__ = "0.1.0"
agk/cli.py ADDED
@@ -0,0 +1,189 @@
1
+ """agk CLI — アプリのスキャフォールド生成。
2
+
3
+ 使い方::
4
+
5
+ agk new <name> [--lang python|julia]
6
+ [--viewer media|cards|field2d|gltf|mesh|webgpu|none]
7
+ [--dir .]
8
+
9
+ `agk/templates/<lang>/` の ``*.tmpl`` を ``str.format`` 置換
10
+ ({name} / {viewer_script} / {viewer_example} / {viewer_note})で展開する。
11
+ テンプレート内の literal な波括弧は ``{{`` / ``}}`` でエスケープする。
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import re
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ __all__ = ["main"]
22
+
23
+ TEMPLATES_DIR = Path(__file__).resolve().parent / "templates"
24
+ LANGS = ("python", "julia")
25
+ VIEWERS = ("media", "cards", "field2d", "gltf", "mesh", "webgpu", "none")
26
+
27
+ NAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
28
+
29
+ # viewer 名 → dist 配布モジュール(/agk/ 配下)。none はインポートなし。
30
+ _VIEWER_MODULES = {v: f"agk-viewer-{v}.js" for v in VIEWERS if v != "none"}
31
+
32
+ # viewer 名 → app.py に埋めるコメント付き成果物例(protocol.md §5)。
33
+ _PY_EXAMPLES = {
34
+ "media": '''\
35
+ # --- 成果物(media ビューア: image / video)---------------------------
36
+ # png = ctx.workspace / "plot.png"
37
+ # png.write_bytes(...) # 画像を生成
38
+ # ctx.emit.artifact("image", url="/artifact/plot.png", mime="image/png")
39
+ ''',
40
+ "cards": '''\
41
+ # --- 成果物(cards ビューア)------------------------------------------
42
+ # import json
43
+ # cards = [{"title": "result A", "snippet": "...", "badges": ["ok"]}]
44
+ # path = ctx.workspace / "cards.json"
45
+ # path.write_text(json.dumps(cards, ensure_ascii=False), encoding="utf-8")
46
+ # ctx.emit.artifact("cards", url="/artifact/cards.json",
47
+ # mime="application/json", replace=False)
48
+ ''',
49
+ "field2d": '''\
50
+ # --- 成果物(field2d ビューア: 2D スカラー場グリッド)------------------
51
+ # import json
52
+ # nx, ny = 32, 32
53
+ # data = {"nx": nx, "ny": ny,
54
+ # "fields": [{"name": "u", "data": [0.0] * (nx * ny)}]}
55
+ # path = ctx.workspace / "field.json"
56
+ # path.write_text(json.dumps(data), encoding="utf-8")
57
+ # ctx.emit.artifact("field2d", url="/artifact/field.json",
58
+ # mime="application/json")
59
+ ''',
60
+ "gltf": '''\
61
+ # --- 成果物(gltf ビューア: GLB / glTF)--------------------------------
62
+ # glb = ctx.workspace / "model.glb"
63
+ # glb.write_bytes(...) # GLB を生成
64
+ # ctx.emit.artifact("gltf", url="/artifact/model.glb",
65
+ # mime="model/gltf-binary", meta={"up": "z"})
66
+ ''',
67
+ "mesh": '''\
68
+ # --- 成果物(mesh ビューア: FEM メッシュ+結果)-------------------------
69
+ # path = ctx.workspace / "mesh.json"
70
+ # path.write_text(...) # メッシュ+フィールドを書き出す
71
+ # ctx.emit.artifact("mesh", url="/artifact/mesh.json",
72
+ # meta={"fields": [{"name": "stress", "unit": "MPa"}]})
73
+ ''',
74
+ "webgpu": '''\
75
+ # --- 成果物(webgpu ビューア: 大規模粒子/格子の GPU 描画)---------------
76
+ # 標準 kind(field2d 等)かアプリ固有 kind("x-<app>.<name>")を送る。
77
+ # ctx.emit.artifact("field2d", url="/artifact/field.json",
78
+ # mime="application/json")
79
+ ''',
80
+ "none": '''\
81
+ # --- 成果物(組み込みビューア: markdown / image / video)---------------
82
+ # md = ctx.workspace / "result.md"
83
+ # md.write_text("## 結果\\n", encoding="utf-8")
84
+ # ctx.emit.artifact("markdown", url="/artifact/result.md",
85
+ # mime="text/markdown")
86
+ ''',
87
+ }
88
+
89
+ # viewer 名 → app.jl に埋めるコメント例。
90
+ _JL_EXAMPLES = {
91
+ "none": '''\
92
+ # path = joinpath(ctx.workspace, "result.md")
93
+ # write(path, "## 結果\\n")
94
+ # emit(ctx, "artifact"; kind="markdown", url="/artifact/result.md",
95
+ # mime="text/markdown")''',
96
+ }
97
+
98
+
99
+ def _jl_example(viewer: str) -> str:
100
+ if viewer in _JL_EXAMPLES:
101
+ return _JL_EXAMPLES[viewer]
102
+ return (
103
+ f' # emit(ctx, "artifact"; kind="{viewer if viewer != "media" else "image"}",\n'
104
+ f' # url="/artifact/result", mime="application/octet-stream")'
105
+ )
106
+
107
+
108
+ def _viewer_script(viewer: str) -> str:
109
+ """index.html に挿す viewer モジュールの <script> 行(none は空)。"""
110
+ module = _VIEWER_MODULES.get(viewer)
111
+ if module is None:
112
+ return ""
113
+ return f'<script type="module" src="/agk/{module}"></script>\n'
114
+
115
+
116
+ def _viewer_note(viewer: str) -> str:
117
+ """README に挿す viewer の補足(none は空)。"""
118
+ module = _VIEWER_MODULES.get(viewer)
119
+ if module is None:
120
+ return ""
121
+ return (
122
+ f"\n## ビューア\n\n"
123
+ f"`{viewer}` ビューア(`/agk/{module}`)を index.html でインポート済み。\n"
124
+ f"対応する artifact イベントの契約は `spec/protocol.md` §5 を参照。\n"
125
+ )
126
+
127
+
128
+ def cmd_new(args: argparse.Namespace) -> int:
129
+ if not NAME_RE.match(args.name):
130
+ print(f"agk new: 不正なアプリ名です: {args.name!r} "
131
+ "(英数字で始まり、英数字と . _ - のみ使用可)", file=sys.stderr)
132
+ return 2
133
+
134
+ src_dir = TEMPLATES_DIR / args.lang
135
+ if not src_dir.is_dir():
136
+ print(f"agk new: テンプレートが見つかりません: {src_dir}", file=sys.stderr)
137
+ return 2
138
+
139
+ target = Path(args.dir).expanduser().resolve() / args.name
140
+ if target.exists() and any(target.iterdir()):
141
+ print(f"agk new: 出力先が空ではありません: {target}", file=sys.stderr)
142
+ return 2
143
+
144
+ subs = {
145
+ "name": args.name,
146
+ "viewer_kind": args.viewer,
147
+ "viewer_script": _viewer_script(args.viewer),
148
+ "viewer_note": _viewer_note(args.viewer),
149
+ "viewer_example": (_PY_EXAMPLES[args.viewer] if args.lang == "python"
150
+ else _jl_example(args.viewer)),
151
+ }
152
+
153
+ target.mkdir(parents=True, exist_ok=True)
154
+ created: list[str] = []
155
+ for tmpl in sorted(src_dir.glob("*.tmpl")):
156
+ out = target / tmpl.name.removesuffix(".tmpl")
157
+ out.write_text(tmpl.read_text(encoding="utf-8").format(**subs),
158
+ encoding="utf-8")
159
+ created.append(out.name)
160
+
161
+ print(f"created: {target}")
162
+ for name in created:
163
+ print(f" {name}")
164
+ return 0
165
+
166
+
167
+ def main(argv: list[str] | None = None) -> int:
168
+ parser = argparse.ArgumentParser(
169
+ prog="agk", description="app-gui-kit CLI(アプリのスキャフォールド生成)")
170
+ sub = parser.add_subparsers(dest="command", required=True)
171
+
172
+ new = sub.add_parser("new", help="新規アプリの雛形を生成する")
173
+ new.add_argument("name", help="アプリ名(生成するディレクトリ名)")
174
+ new.add_argument("--lang", choices=LANGS, default="python",
175
+ help="サーバ言語(既定: python)")
176
+ new.add_argument("--viewer", choices=VIEWERS, default="none",
177
+ help="使用する標準ビューア(既定: none)")
178
+ new.add_argument("--dir", default=".",
179
+ help="生成先の親ディレクトリ(既定: カレント)")
180
+
181
+ args = parser.parse_args(argv)
182
+ if args.command == "new":
183
+ return cmd_new(args)
184
+ parser.error(f"unknown command: {args.command}")
185
+ return 2 # unreachable
186
+
187
+
188
+ if __name__ == "__main__":
189
+ sys.exit(main())