namel3ss 0.1.0a0__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.
Files changed (157) hide show
  1. namel3ss/__init__.py +4 -0
  2. namel3ss/ast/__init__.py +5 -0
  3. namel3ss/ast/agents.py +13 -0
  4. namel3ss/ast/ai.py +23 -0
  5. namel3ss/ast/base.py +10 -0
  6. namel3ss/ast/expressions.py +55 -0
  7. namel3ss/ast/nodes.py +86 -0
  8. namel3ss/ast/pages.py +43 -0
  9. namel3ss/ast/program.py +22 -0
  10. namel3ss/ast/records.py +27 -0
  11. namel3ss/ast/statements.py +107 -0
  12. namel3ss/ast/tool.py +11 -0
  13. namel3ss/cli/__init__.py +2 -0
  14. namel3ss/cli/actions_mode.py +39 -0
  15. namel3ss/cli/app_loader.py +22 -0
  16. namel3ss/cli/commands/action.py +27 -0
  17. namel3ss/cli/commands/run.py +43 -0
  18. namel3ss/cli/commands/ui.py +26 -0
  19. namel3ss/cli/commands/validate.py +23 -0
  20. namel3ss/cli/format_mode.py +30 -0
  21. namel3ss/cli/io/json_io.py +19 -0
  22. namel3ss/cli/io/read_source.py +16 -0
  23. namel3ss/cli/json_io.py +21 -0
  24. namel3ss/cli/lint_mode.py +29 -0
  25. namel3ss/cli/main.py +135 -0
  26. namel3ss/cli/new_mode.py +146 -0
  27. namel3ss/cli/runner.py +28 -0
  28. namel3ss/cli/studio_mode.py +22 -0
  29. namel3ss/cli/ui_mode.py +14 -0
  30. namel3ss/config/__init__.py +4 -0
  31. namel3ss/config/dotenv.py +33 -0
  32. namel3ss/config/loader.py +83 -0
  33. namel3ss/config/model.py +49 -0
  34. namel3ss/errors/__init__.py +2 -0
  35. namel3ss/errors/base.py +34 -0
  36. namel3ss/errors/render.py +22 -0
  37. namel3ss/format/__init__.py +3 -0
  38. namel3ss/format/formatter.py +18 -0
  39. namel3ss/format/rules.py +97 -0
  40. namel3ss/ir/__init__.py +3 -0
  41. namel3ss/ir/lowering/__init__.py +4 -0
  42. namel3ss/ir/lowering/agents.py +42 -0
  43. namel3ss/ir/lowering/ai.py +45 -0
  44. namel3ss/ir/lowering/expressions.py +49 -0
  45. namel3ss/ir/lowering/flow.py +21 -0
  46. namel3ss/ir/lowering/pages.py +48 -0
  47. namel3ss/ir/lowering/program.py +34 -0
  48. namel3ss/ir/lowering/records.py +25 -0
  49. namel3ss/ir/lowering/statements.py +122 -0
  50. namel3ss/ir/lowering/tools.py +16 -0
  51. namel3ss/ir/model/__init__.py +50 -0
  52. namel3ss/ir/model/agents.py +33 -0
  53. namel3ss/ir/model/ai.py +31 -0
  54. namel3ss/ir/model/base.py +20 -0
  55. namel3ss/ir/model/expressions.py +50 -0
  56. namel3ss/ir/model/pages.py +43 -0
  57. namel3ss/ir/model/program.py +28 -0
  58. namel3ss/ir/model/statements.py +76 -0
  59. namel3ss/ir/model/tools.py +11 -0
  60. namel3ss/ir/nodes.py +88 -0
  61. namel3ss/lexer/__init__.py +2 -0
  62. namel3ss/lexer/lexer.py +152 -0
  63. namel3ss/lexer/tokens.py +98 -0
  64. namel3ss/lint/__init__.py +4 -0
  65. namel3ss/lint/engine.py +125 -0
  66. namel3ss/lint/semantic.py +45 -0
  67. namel3ss/lint/text_scan.py +70 -0
  68. namel3ss/lint/types.py +22 -0
  69. namel3ss/parser/__init__.py +3 -0
  70. namel3ss/parser/agent.py +78 -0
  71. namel3ss/parser/ai.py +113 -0
  72. namel3ss/parser/constraints.py +37 -0
  73. namel3ss/parser/core.py +166 -0
  74. namel3ss/parser/expressions.py +105 -0
  75. namel3ss/parser/flow.py +37 -0
  76. namel3ss/parser/pages.py +76 -0
  77. namel3ss/parser/program.py +45 -0
  78. namel3ss/parser/records.py +66 -0
  79. namel3ss/parser/statements/__init__.py +27 -0
  80. namel3ss/parser/statements/control_flow.py +116 -0
  81. namel3ss/parser/statements/core.py +66 -0
  82. namel3ss/parser/statements/data.py +17 -0
  83. namel3ss/parser/statements/letset.py +22 -0
  84. namel3ss/parser/statements.py +1 -0
  85. namel3ss/parser/tokens.py +35 -0
  86. namel3ss/parser/tool.py +29 -0
  87. namel3ss/runtime/__init__.py +3 -0
  88. namel3ss/runtime/ai/http/client.py +24 -0
  89. namel3ss/runtime/ai/mock_provider.py +5 -0
  90. namel3ss/runtime/ai/provider.py +29 -0
  91. namel3ss/runtime/ai/providers/__init__.py +18 -0
  92. namel3ss/runtime/ai/providers/_shared/errors.py +20 -0
  93. namel3ss/runtime/ai/providers/_shared/parse.py +18 -0
  94. namel3ss/runtime/ai/providers/anthropic.py +55 -0
  95. namel3ss/runtime/ai/providers/gemini.py +50 -0
  96. namel3ss/runtime/ai/providers/mistral.py +51 -0
  97. namel3ss/runtime/ai/providers/mock.py +23 -0
  98. namel3ss/runtime/ai/providers/ollama.py +39 -0
  99. namel3ss/runtime/ai/providers/openai.py +55 -0
  100. namel3ss/runtime/ai/providers/registry.py +38 -0
  101. namel3ss/runtime/ai/trace.py +18 -0
  102. namel3ss/runtime/executor/__init__.py +3 -0
  103. namel3ss/runtime/executor/agents.py +91 -0
  104. namel3ss/runtime/executor/ai_runner.py +90 -0
  105. namel3ss/runtime/executor/api.py +54 -0
  106. namel3ss/runtime/executor/assign.py +40 -0
  107. namel3ss/runtime/executor/context.py +31 -0
  108. namel3ss/runtime/executor/executor.py +77 -0
  109. namel3ss/runtime/executor/expr_eval.py +110 -0
  110. namel3ss/runtime/executor/records_ops.py +64 -0
  111. namel3ss/runtime/executor/result.py +13 -0
  112. namel3ss/runtime/executor/signals.py +6 -0
  113. namel3ss/runtime/executor/statements.py +99 -0
  114. namel3ss/runtime/memory/manager.py +52 -0
  115. namel3ss/runtime/memory/profile.py +17 -0
  116. namel3ss/runtime/memory/semantic.py +20 -0
  117. namel3ss/runtime/memory/short_term.py +18 -0
  118. namel3ss/runtime/records/service.py +105 -0
  119. namel3ss/runtime/store/__init__.py +2 -0
  120. namel3ss/runtime/store/memory_store.py +62 -0
  121. namel3ss/runtime/tools/registry.py +13 -0
  122. namel3ss/runtime/ui/__init__.py +2 -0
  123. namel3ss/runtime/ui/actions.py +124 -0
  124. namel3ss/runtime/validators/__init__.py +2 -0
  125. namel3ss/runtime/validators/constraints.py +126 -0
  126. namel3ss/schema/__init__.py +2 -0
  127. namel3ss/schema/records.py +52 -0
  128. namel3ss/studio/__init__.py +4 -0
  129. namel3ss/studio/api.py +115 -0
  130. namel3ss/studio/edit/__init__.py +3 -0
  131. namel3ss/studio/edit/ops.py +80 -0
  132. namel3ss/studio/edit/selectors.py +74 -0
  133. namel3ss/studio/edit/transform.py +39 -0
  134. namel3ss/studio/server.py +175 -0
  135. namel3ss/studio/session.py +11 -0
  136. namel3ss/studio/web/app.js +248 -0
  137. namel3ss/studio/web/index.html +44 -0
  138. namel3ss/studio/web/styles.css +42 -0
  139. namel3ss/templates/__init__.py +3 -0
  140. namel3ss/templates/__pycache__/__init__.cpython-312.pyc +0 -0
  141. namel3ss/templates/ai_assistant/.gitignore +1 -0
  142. namel3ss/templates/ai_assistant/README.md +10 -0
  143. namel3ss/templates/ai_assistant/app.ai +30 -0
  144. namel3ss/templates/crud/.gitignore +1 -0
  145. namel3ss/templates/crud/README.md +10 -0
  146. namel3ss/templates/crud/app.ai +26 -0
  147. namel3ss/templates/multi_agent/.gitignore +1 -0
  148. namel3ss/templates/multi_agent/README.md +10 -0
  149. namel3ss/templates/multi_agent/app.ai +43 -0
  150. namel3ss/ui/__init__.py +2 -0
  151. namel3ss/ui/manifest.py +220 -0
  152. namel3ss/utils/__init__.py +2 -0
  153. namel3ss-0.1.0a0.dist-info/METADATA +123 -0
  154. namel3ss-0.1.0a0.dist-info/RECORD +157 -0
  155. namel3ss-0.1.0a0.dist-info/WHEEL +5 -0
  156. namel3ss-0.1.0a0.dist-info/entry_points.txt +2 -0
  157. namel3ss-0.1.0a0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,175 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from http.server import HTTPServer, SimpleHTTPRequestHandler
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from namel3ss.errors.base import Namel3ssError
10
+ from namel3ss.errors.render import format_error
11
+ from namel3ss.studio.api import (
12
+ apply_edit,
13
+ execute_action,
14
+ get_actions_payload,
15
+ get_lint_payload,
16
+ get_summary_payload,
17
+ get_ui_payload,
18
+ )
19
+ from namel3ss.studio.session import SessionState
20
+
21
+
22
+ class StudioRequestHandler(SimpleHTTPRequestHandler):
23
+ def log_message(self, format: str, *args: Any) -> None: # pragma: no cover - silence
24
+ pass
25
+
26
+ def _read_source(self) -> str:
27
+ path = Path(self.server.app_path) # type: ignore[attr-defined]
28
+ return path.read_text(encoding="utf-8")
29
+
30
+ def _get_session(self) -> SessionState:
31
+ return self.server.session_state # type: ignore[attr-defined]
32
+
33
+ def _respond_json(self, payload: dict, status: int = 200) -> None:
34
+ data = json.dumps(payload).encode("utf-8")
35
+ self.send_response(status)
36
+ self.send_header("Content-Type", "application/json")
37
+ self.send_header("Content-Length", str(len(data)))
38
+ self.end_headers()
39
+ self.wfile.write(data)
40
+
41
+ def do_GET(self) -> None: # noqa: N802
42
+ if self.path.startswith("/api/"):
43
+ self.handle_api()
44
+ return
45
+ self.handle_static()
46
+
47
+ def do_POST(self) -> None: # noqa: N802
48
+ if self.path.startswith("/api/"):
49
+ self.handle_api_post()
50
+ return
51
+ self.send_error(404)
52
+
53
+ def handle_static(self) -> None:
54
+ web_root = Path(__file__).parent / "web"
55
+ if self.path in {"/", "/index.html"}:
56
+ file_path = web_root / "index.html"
57
+ else:
58
+ file_path = web_root / self.path.lstrip("/")
59
+ if not file_path.exists():
60
+ self.send_error(404)
61
+ return
62
+ content = file_path.read_bytes()
63
+ content_type = "text/html"
64
+ if file_path.suffix == ".js":
65
+ content_type = "application/javascript"
66
+ if file_path.suffix == ".css":
67
+ content_type = "text/css"
68
+ self.send_response(200)
69
+ self.send_header("Content-Type", content_type)
70
+ self.send_header("Content-Length", str(len(content)))
71
+ self.end_headers()
72
+ self.wfile.write(content)
73
+
74
+ def handle_api(self) -> None:
75
+ try:
76
+ source = self._read_source()
77
+ except Exception as err: # pragma: no cover - IO error edge
78
+ payload = {"ok": False, "error": f"Cannot read source: {err}"}
79
+ self._respond_json(payload, status=500)
80
+ return
81
+ if self.path == "/api/summary":
82
+ payload = get_summary_payload(source, self.server.app_path) # type: ignore[attr-defined]
83
+ status = 200 if payload.get("ok") else 400
84
+ self._respond_json(payload, status=status)
85
+ return
86
+ if self.path == "/api/ui":
87
+ payload = get_ui_payload(source, self._get_session())
88
+ status = 200 if payload.get("ok", True) else 400
89
+ self._respond_json(payload, status=status)
90
+ return
91
+ if self.path == "/api/actions":
92
+ payload = get_actions_payload(source)
93
+ status = 200 if payload.get("ok") else 400
94
+ self._respond_json(payload, status=status)
95
+ return
96
+ if self.path == "/api/lint":
97
+ payload = get_lint_payload(source)
98
+ self._respond_json(payload, status=200)
99
+ return
100
+ self.send_error(404)
101
+
102
+ def handle_api_post(self) -> None:
103
+ length = int(self.headers.get("Content-Length", "0"))
104
+ raw_body = self.rfile.read(length) if length else b""
105
+ try:
106
+ body = json.loads(raw_body.decode("utf-8") or "{}")
107
+ except json.JSONDecodeError:
108
+ self._respond_json({"ok": False, "error": "Invalid JSON body"}, status=400)
109
+ return
110
+ try:
111
+ source = self._read_source()
112
+ except Exception as err: # pragma: no cover
113
+ payload = {"ok": False, "error": f"Cannot read source: {err}"}
114
+ self._respond_json(payload, status=500)
115
+ return
116
+ if self.path == "/api/edit":
117
+ if not isinstance(body, dict):
118
+ self._respond_json({"ok": False, "error": "Body must be a JSON object"}, status=400)
119
+ return
120
+ op = body.get("op")
121
+ target = body.get("target")
122
+ value = body.get("value", "")
123
+ if not isinstance(op, str):
124
+ self._respond_json({"ok": False, "error": "Edit op is required"}, status=400)
125
+ return
126
+ if not isinstance(target, dict):
127
+ self._respond_json({"ok": False, "error": "Edit target is required"}, status=400)
128
+ return
129
+ if not isinstance(value, str):
130
+ self._respond_json({"ok": False, "error": "Edit value must be a string"}, status=400)
131
+ return
132
+ try:
133
+ resp = apply_edit(self.server.app_path, op, target, value, self._get_session()) # type: ignore[attr-defined]
134
+ self._respond_json(resp, status=200)
135
+ return
136
+ except Namel3ssError as err:
137
+ self._respond_json({"ok": False, "error": format_error(err, source)}, status=400)
138
+ return
139
+ if self.path == "/api/action":
140
+ if not isinstance(body, dict):
141
+ self._respond_json({"ok": False, "error": "Body must be a JSON object"}, status=400)
142
+ return
143
+ action_id = body.get("id")
144
+ payload = body.get("payload") or {}
145
+ if not isinstance(action_id, str):
146
+ self._respond_json({"ok": False, "error": "Action id is required"}, status=400)
147
+ return
148
+ if not isinstance(payload, dict):
149
+ self._respond_json({"ok": False, "error": "Payload must be an object"}, status=400)
150
+ return
151
+ try:
152
+ resp = execute_action(source, self._get_session(), action_id, payload)
153
+ status = 200 if resp.get("ok", True) else 200
154
+ self._respond_json(resp, status=status)
155
+ return
156
+ except Namel3ssError as err:
157
+ self._respond_json({"ok": False, "error": format_error(err, source)}, status=400)
158
+ return
159
+ if self.path == "/api/reset":
160
+ self.server.session_state = SessionState() # type: ignore[attr-defined]
161
+ self._respond_json({"ok": True}, status=200)
162
+ return
163
+ self.send_error(404)
164
+
165
+
166
+ def start_server(app_path: str, port: int) -> None:
167
+ handler = StudioRequestHandler
168
+ server = HTTPServer(("127.0.0.1", port), handler)
169
+ server.app_path = app_path # type: ignore[attr-defined]
170
+ server.session_state = SessionState() # type: ignore[attr-defined]
171
+ print(f"Studio: http://127.0.0.1:{port}/")
172
+ try:
173
+ server.serve_forever()
174
+ finally:
175
+ server.server_close()
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+ from namel3ss.runtime.store.memory_store import MemoryStore
6
+
7
+
8
+ @dataclass
9
+ class SessionState:
10
+ state: dict = field(default_factory=dict)
11
+ store: MemoryStore = field(default_factory=MemoryStore)
@@ -0,0 +1,248 @@
1
+ async function fetchJson(path, options) {
2
+ const res = await fetch(path, options);
3
+ return res.json();
4
+ }
5
+
6
+ function renderSummary(data) {
7
+ const el = document.getElementById("summary");
8
+ if (!data.ok) {
9
+ el.textContent = data.error || "Error";
10
+ return;
11
+ }
12
+ el.textContent = JSON.stringify(data.counts, null, 2);
13
+ }
14
+
15
+ function renderActions(data) {
16
+ const el = document.getElementById("actions");
17
+ if (!data.ok) {
18
+ el.textContent = data.error || "Error";
19
+ return;
20
+ }
21
+ el.textContent = data.actions
22
+ .map((a) => {
23
+ const parts = [a.id, a.type];
24
+ if (a.flow) parts.push(`flow=${a.flow}`);
25
+ if (a.record) parts.push(`record=${a.record}`);
26
+ return parts.join(" ");
27
+ })
28
+ .join("\n");
29
+ }
30
+
31
+ function renderLint(data) {
32
+ const el = document.getElementById("lint");
33
+ if (!data.ok && data.error) {
34
+ el.textContent = data.error;
35
+ return;
36
+ }
37
+ el.textContent =
38
+ data.findings
39
+ .map((f) => `${f.severity} ${f.code} ${f.message} (${f.line}:${f.column})`)
40
+ .join("\n") || "OK";
41
+ }
42
+
43
+ function renderState(data) {
44
+ const el = document.getElementById("state");
45
+ el.textContent = data ? JSON.stringify(data, null, 2) : "{}";
46
+ }
47
+
48
+ function renderTraces(data) {
49
+ const el = document.getElementById("traces");
50
+ el.textContent = data ? JSON.stringify(data, null, 2) : "[]";
51
+ }
52
+
53
+ async function executeAction(actionId, payload) {
54
+ const res = await fetch("/api/action", {
55
+ method: "POST",
56
+ headers: { "Content-Type": "application/json" },
57
+ body: JSON.stringify({ id: actionId, payload }),
58
+ });
59
+ const data = await res.json();
60
+ if (!data.ok && data.error) {
61
+ alert(data.error);
62
+ }
63
+ if (!data.ok && data.errors) {
64
+ return data;
65
+ }
66
+ if (data.state) {
67
+ renderState(data.state);
68
+ }
69
+ if (data.traces) {
70
+ renderTraces(data.traces);
71
+ }
72
+ if (data.ui) {
73
+ renderUI(data.ui);
74
+ }
75
+ return data;
76
+ }
77
+
78
+ async function performEdit(op, elementId, pageName, value) {
79
+ const res = await fetch("/api/edit", {
80
+ method: "POST",
81
+ headers: { "Content-Type": "application/json" },
82
+ body: JSON.stringify({ op, target: { element_id: elementId, page: pageName }, value }),
83
+ });
84
+ const data = await res.json();
85
+ if (!data.ok) {
86
+ alert(data.error || "Edit failed");
87
+ return;
88
+ }
89
+ renderSummary(data.summary);
90
+ renderActions(data.actions);
91
+ renderLint(data.lint);
92
+ if (data.ui) {
93
+ renderUI(data.ui);
94
+ }
95
+ }
96
+
97
+ function renderUI(manifest) {
98
+ const select = document.getElementById("pageSelect");
99
+ const uiContainer = document.getElementById("ui");
100
+ const pages = manifest.pages || [];
101
+ const currentSelection = select.value;
102
+ select.innerHTML = "";
103
+ pages.forEach((p, idx) => {
104
+ const opt = document.createElement("option");
105
+ opt.value = p.name;
106
+ opt.textContent = p.name;
107
+ if (p.name === currentSelection || (currentSelection === "" && idx === 0)) {
108
+ opt.selected = true;
109
+ }
110
+ select.appendChild(opt);
111
+ });
112
+
113
+ function renderPage(pageName) {
114
+ uiContainer.innerHTML = "";
115
+ const page = pages.find((p) => p.name === pageName) || pages[0];
116
+ if (!page) return;
117
+ page.elements.forEach((el) => {
118
+ const div = document.createElement("div");
119
+ div.className = "element";
120
+ if (el.type === "title") {
121
+ const h = document.createElement("h3");
122
+ h.textContent = el.value;
123
+ div.appendChild(h);
124
+ const btn = document.createElement("button");
125
+ btn.textContent = "Edit";
126
+ btn.onclick = () => showEditField(el, page.name, "set_title");
127
+ div.appendChild(btn);
128
+ } else if (el.type === "text") {
129
+ const p = document.createElement("p");
130
+ p.textContent = el.value;
131
+ div.appendChild(p);
132
+ const btn = document.createElement("button");
133
+ btn.textContent = "Edit";
134
+ btn.onclick = () => showEditField(el, page.name, "set_text");
135
+ div.appendChild(btn);
136
+ } else if (el.type === "button") {
137
+ const btn = document.createElement("button");
138
+ btn.textContent = el.label;
139
+ btn.onclick = () => executeAction(el.action_id, {});
140
+ div.appendChild(btn);
141
+ const rename = document.createElement("button");
142
+ rename.textContent = "Rename";
143
+ rename.onclick = () => showEditField(el, page.name, "set_button_label");
144
+ div.appendChild(rename);
145
+ } else if (el.type === "form") {
146
+ const form = document.createElement("form");
147
+ form.innerHTML = `<strong>Form: ${el.record}</strong>`;
148
+ (el.fields || []).forEach((f) => {
149
+ const label = document.createElement("label");
150
+ label.textContent = f.name;
151
+ const input = document.createElement("input");
152
+ input.name = f.name;
153
+ label.appendChild(input);
154
+ form.appendChild(label);
155
+ });
156
+ const submit = document.createElement("button");
157
+ submit.type = "submit";
158
+ submit.textContent = "Submit";
159
+ form.appendChild(submit);
160
+ const errors = document.createElement("div");
161
+ errors.className = "errors";
162
+ form.appendChild(errors);
163
+ form.onsubmit = async (e) => {
164
+ e.preventDefault();
165
+ const values = {};
166
+ (el.fields || []).forEach((f) => {
167
+ const input = form.querySelector(`input[name="${f.name}"]`);
168
+ values[f.name] = input ? input.value : "";
169
+ });
170
+ const result = await executeAction(el.action_id, { values });
171
+ if (!result.ok && result.errors) {
172
+ errors.textContent = result.errors.map((err) => `${err.field}: ${err.message}`).join("; ");
173
+ } else if (!result.ok && result.error) {
174
+ errors.textContent = result.error;
175
+ } else {
176
+ errors.textContent = "";
177
+ }
178
+ };
179
+ div.appendChild(form);
180
+ } else if (el.type === "table") {
181
+ const table = document.createElement("table");
182
+ const header = document.createElement("tr");
183
+ (el.columns || []).forEach((c) => {
184
+ const th = document.createElement("th");
185
+ th.textContent = c.name;
186
+ header.appendChild(th);
187
+ });
188
+ table.appendChild(header);
189
+ (el.rows || []).forEach((row) => {
190
+ const tr = document.createElement("tr");
191
+ (el.columns || []).forEach((c) => {
192
+ const td = document.createElement("td");
193
+ td.textContent = row[c.name] ?? "";
194
+ tr.appendChild(td);
195
+ });
196
+ table.appendChild(tr);
197
+ });
198
+ div.appendChild(table);
199
+ }
200
+ uiContainer.appendChild(div);
201
+ });
202
+ }
203
+
204
+ select.onchange = (e) => renderPage(e.target.value);
205
+ const initialPage = select.value || (pages[0] ? pages[0].name : "");
206
+ if (initialPage) {
207
+ renderPage(initialPage);
208
+ } else {
209
+ uiContainer.textContent = "No pages";
210
+ }
211
+ }
212
+
213
+ function showEditField(element, pageName, op) {
214
+ const newValue = prompt("Enter new value", element.value || element.label || "");
215
+ if (newValue === null) {
216
+ return;
217
+ }
218
+ performEdit(op, element.element_id, pageName, newValue);
219
+ }
220
+
221
+ async function refreshAll() {
222
+ const [summary, ui, actions, lint] = await Promise.all([
223
+ fetchJson("/api/summary"),
224
+ fetchJson("/api/ui"),
225
+ fetchJson("/api/actions"),
226
+ fetchJson("/api/lint"),
227
+ ]);
228
+ renderSummary(summary);
229
+ renderActions(actions);
230
+ renderLint(lint);
231
+ renderState({});
232
+ renderTraces([]);
233
+ if (ui.ok !== false) {
234
+ renderUI(ui);
235
+ } else {
236
+ document.getElementById("ui").textContent = ui.error || "Error";
237
+ }
238
+ }
239
+
240
+ document.getElementById("refresh").onclick = refreshAll;
241
+ document.getElementById("reset").onclick = async () => {
242
+ await fetch("/api/reset", { method: "POST", body: "{}" });
243
+ renderState({});
244
+ renderTraces([]);
245
+ refreshAll();
246
+ };
247
+
248
+ refreshAll();
@@ -0,0 +1,44 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Namel3ss Studio</title>
7
+ <link rel="stylesheet" href="/styles.css">
8
+ </head>
9
+ <body>
10
+ <header>
11
+ <h1>Namel3ss Studio</h1>
12
+ <button id="refresh">Refresh</button>
13
+ <button id="reset">Reset</button>
14
+ </header>
15
+ <main>
16
+ <section>
17
+ <h2>Summary</h2>
18
+ <pre id="summary"></pre>
19
+ </section>
20
+ <section>
21
+ <h2>UI Preview</h2>
22
+ <select id="pageSelect"></select>
23
+ <div id="ui"></div>
24
+ </section>
25
+ <section>
26
+ <h2>State</h2>
27
+ <pre id="state"></pre>
28
+ </section>
29
+ <section>
30
+ <h2>Traces</h2>
31
+ <pre id="traces"></pre>
32
+ </section>
33
+ <section>
34
+ <h2>Actions</h2>
35
+ <pre id="actions"></pre>
36
+ </section>
37
+ <section>
38
+ <h2>Lint Findings</h2>
39
+ <pre id="lint"></pre>
40
+ </section>
41
+ </main>
42
+ <script src="/app.js"></script>
43
+ </body>
44
+ </html>
@@ -0,0 +1,42 @@
1
+ body {
2
+ font-family: Arial, sans-serif;
3
+ margin: 0;
4
+ padding: 0 16px 32px 16px;
5
+ }
6
+
7
+ header {
8
+ display: flex;
9
+ align-items: center;
10
+ gap: 12px;
11
+ border-bottom: 1px solid #ddd;
12
+ padding: 12px 0;
13
+ }
14
+
15
+ main {
16
+ display: grid;
17
+ grid-template-columns: 1fr 1fr;
18
+ gap: 16px;
19
+ margin-top: 16px;
20
+ }
21
+
22
+ section {
23
+ border: 1px solid #eee;
24
+ padding: 12px;
25
+ border-radius: 6px;
26
+ background: #fafafa;
27
+ }
28
+
29
+ pre {
30
+ background: #fff;
31
+ border: 1px solid #eee;
32
+ padding: 8px;
33
+ overflow: auto;
34
+ }
35
+
36
+ .element {
37
+ margin-bottom: 8px;
38
+ }
39
+
40
+ button {
41
+ padding: 6px 12px;
42
+ }
@@ -0,0 +1,3 @@
1
+ """Project templates for `n3 new`."""
2
+
3
+ __all__ = []
@@ -0,0 +1 @@
1
+ .env
@@ -0,0 +1,10 @@
1
+ # {{PROJECT_NAME}} (AI Assistant)
2
+
3
+ Ask an AI assistant over saved notes. Includes an echo tool, memory config, and a notes page with a call-to-action button.
4
+
5
+ Run it with:
6
+ - `n3 app.ai check`
7
+ - `n3 app.ai studio`
8
+ - `n3 app.ai actions`
9
+
10
+ Put any API keys in a local `.env` (not committed).
@@ -0,0 +1,30 @@
1
+ tool "echo":
2
+ kind is "builtin"
3
+
4
+ record "Note":
5
+ title string must be present
6
+ body string must be present
7
+
8
+ ai "assistant":
9
+ model is "gpt-4.1"
10
+ system_prompt is "You are a helpful assistant for {{PROJECT_NAME}}."
11
+ tools:
12
+ expose "echo"
13
+ memory:
14
+ short_term is 5
15
+ semantic is true
16
+ profile is true
17
+
18
+ flow "ask_assistant":
19
+ let prompt is "Summarize the latest notes."
20
+ ask ai "assistant" with input: prompt as reply
21
+ set state.reply is reply
22
+ return reply
23
+
24
+ page "notes":
25
+ title is "{{PROJECT_NAME}} Notes Assistant"
26
+ text is "Save notes and ask the assistant for help."
27
+ form is "Note"
28
+ table is "Note"
29
+ button "Ask assistant":
30
+ calls flow "ask_assistant"
@@ -0,0 +1 @@
1
+ .env
@@ -0,0 +1,10 @@
1
+ # {{PROJECT_NAME}} (CRUD)
2
+
3
+ Starter CRUD dashboard with a customer record, seed flow, and a combined form/table page.
4
+
5
+ Run it with:
6
+ - `n3 app.ai check`
7
+ - `n3 app.ai studio`
8
+ - `n3 app.ai actions`
9
+
10
+ Put any API keys in a local `.env` (not committed).
@@ -0,0 +1,26 @@
1
+ record "Customer":
2
+ name string must be present
3
+ email string must match pattern ".*@.*"
4
+ age int must be greater than 17
5
+
6
+ flow "seed_customers":
7
+ set state.customer.name is "Alice"
8
+ set state.customer.email is "alice@example.com"
9
+ set state.customer.age is 21
10
+ save Customer
11
+ set state.customer.name is "Bob"
12
+ set state.customer.email is "bob@example.com"
13
+ set state.customer.age is 30
14
+ save Customer
15
+ return "seeded"
16
+
17
+ flow "hello":
18
+ return "Hello from {{PROJECT_NAME}}"
19
+
20
+ page "home":
21
+ title is "{{PROJECT_NAME}} Dashboard"
22
+ text is "Add customers and view them below."
23
+ button "Seed demo data":
24
+ calls flow "seed_customers"
25
+ form is "Customer"
26
+ table is "Customer"
@@ -0,0 +1 @@
1
+ .env
@@ -0,0 +1,10 @@
1
+ # {{PROJECT_NAME}} (Multi-Agent)
2
+
3
+ Planner, critic, and researcher agents collaborating on a shared assistant with a single workflow page.
4
+
5
+ Run it with:
6
+ - `n3 app.ai check`
7
+ - `n3 app.ai studio`
8
+ - `n3 app.ai actions`
9
+
10
+ Put any API keys in a local `.env` (not committed).
@@ -0,0 +1,43 @@
1
+ tool "echo":
2
+ kind is "builtin"
3
+
4
+ ai "assistant":
5
+ model is "gpt-4.1"
6
+ system_prompt is "You are a helpful multi-agent assistant for {{PROJECT_NAME}}."
7
+ tools:
8
+ expose "echo"
9
+ memory:
10
+ short_term is 5
11
+ semantic is true
12
+ profile is true
13
+
14
+ agent "planner":
15
+ ai is "assistant"
16
+ system_prompt is "Plan step-by-step."
17
+
18
+ agent "critic":
19
+ ai is "assistant"
20
+ system_prompt is "Find risks and gaps."
21
+
22
+ agent "researcher":
23
+ ai is "assistant"
24
+ system_prompt is "Add supporting details."
25
+
26
+ flow "run_workflow":
27
+ let task is "Plan a 3-step launch checklist for {{PROJECT_NAME}}"
28
+ run agent "planner" with input: task as plan
29
+ run agents in parallel:
30
+ agent "critic" with input: plan
31
+ agent "researcher" with input: plan
32
+ as feedback
33
+ ask ai "assistant" with input: plan as final
34
+ set state.plan is plan
35
+ set state.feedback is feedback
36
+ set state.final is final
37
+ return final
38
+
39
+ page "workflow":
40
+ title is "{{PROJECT_NAME}} Multi-Agent Workflow"
41
+ text is "Planner plus critic and researcher."
42
+ button "Run workflow":
43
+ calls flow "run_workflow"
@@ -0,0 +1,2 @@
1
+ """UI utilities for Namel3ss."""
2
+