jac-coder 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.
Files changed (85) hide show
  1. jac_coder/__init__.jac +0 -0
  2. jac_coder/api.jac +82 -0
  3. jac_coder/cli_entry.py +25 -0
  4. jac_coder/config.jac +36 -0
  5. jac_coder/context.jac +17 -0
  6. jac_coder/data/examples/ai_agent.md +90 -0
  7. jac_coder/data/examples/blog_app.md +386 -0
  8. jac_coder/data/examples/core_patterns.md +321 -0
  9. jac_coder/data/examples/todo_app.md +321 -0
  10. jac_coder/data/reference/ai.md +131 -0
  11. jac_coder/data/reference/backend.md +215 -0
  12. jac_coder/data/reference/frontend.md +271 -0
  13. jac_coder/data/reference/osp.md +229 -0
  14. jac_coder/data/reference/pitfalls.md +141 -0
  15. jac_coder/data/reference/syntax.md +159 -0
  16. jac_coder/data/rules/core_jac.md +559 -0
  17. jac_coder/data/rules/fullstack.md +362 -0
  18. jac_coder/data/rules/workflow.md +88 -0
  19. jac_coder/events.jac +110 -0
  20. jac_coder/impl/api.impl.jac +399 -0
  21. jac_coder/impl/config.impl.jac +163 -0
  22. jac_coder/impl/context.impl.jac +117 -0
  23. jac_coder/impl/mcp_manager.impl.jac +380 -0
  24. jac_coder/impl/memory.impl.jac +247 -0
  25. jac_coder/impl/nodes.impl.jac +259 -0
  26. jac_coder/impl/permission.impl.jac +62 -0
  27. jac_coder/impl/walkers.impl.jac +298 -0
  28. jac_coder/mcp_manager.jac +35 -0
  29. jac_coder/memory.jac +15 -0
  30. jac_coder/nodes.jac +306 -0
  31. jac_coder/permission.jac +19 -0
  32. jac_coder/serve_entry.jac +30 -0
  33. jac_coder/server.jac +324 -0
  34. jac_coder/tool/__init__.jac +17 -0
  35. jac_coder/tool/checked.jac +10 -0
  36. jac_coder/tool/delegation.jac +23 -0
  37. jac_coder/tool/filesystem.jac +25 -0
  38. jac_coder/tool/git.jac +18 -0
  39. jac_coder/tool/guarded.jac +23 -0
  40. jac_coder/tool/impl/checked.impl.jac +38 -0
  41. jac_coder/tool/impl/delegation.impl.jac +157 -0
  42. jac_coder/tool/impl/filesystem.impl.jac +781 -0
  43. jac_coder/tool/impl/git.impl.jac +115 -0
  44. jac_coder/tool/impl/guarded.impl.jac +72 -0
  45. jac_coder/tool/impl/jac_analyzer.impl.jac +593 -0
  46. jac_coder/tool/impl/jac_docs.impl.jac +136 -0
  47. jac_coder/tool/impl/jac_tools.impl.jac +79 -0
  48. jac_coder/tool/impl/mcp.impl.jac +32 -0
  49. jac_coder/tool/impl/preview.impl.jac +233 -0
  50. jac_coder/tool/impl/question.impl.jac +29 -0
  51. jac_coder/tool/impl/scaffold.impl.jac +231 -0
  52. jac_coder/tool/impl/search.impl.jac +85 -0
  53. jac_coder/tool/impl/shell.impl.jac +89 -0
  54. jac_coder/tool/impl/task.impl.jac +12 -0
  55. jac_coder/tool/impl/think.impl.jac +4 -0
  56. jac_coder/tool/impl/todo.impl.jac +58 -0
  57. jac_coder/tool/impl/validate.impl.jac +236 -0
  58. jac_coder/tool/impl/web.impl.jac +91 -0
  59. jac_coder/tool/jac_analyzer.jac +21 -0
  60. jac_coder/tool/jac_docs.jac +9 -0
  61. jac_coder/tool/jac_tools.jac +11 -0
  62. jac_coder/tool/mcp.jac +17 -0
  63. jac_coder/tool/preview.jac +31 -0
  64. jac_coder/tool/question.jac +7 -0
  65. jac_coder/tool/scaffold.jac +10 -0
  66. jac_coder/tool/search.jac +14 -0
  67. jac_coder/tool/shell.jac +12 -0
  68. jac_coder/tool/task.jac +9 -0
  69. jac_coder/tool/think.jac +5 -0
  70. jac_coder/tool/todo.jac +12 -0
  71. jac_coder/tool/validate.jac +11 -0
  72. jac_coder/tool/vision.jac +17 -0
  73. jac_coder/tool/web.jac +10 -0
  74. jac_coder/util/__init__.jac +18 -0
  75. jac_coder/util/colors.jac +20 -0
  76. jac_coder/util/impl/sandbox.impl.jac +38 -0
  77. jac_coder/util/impl/tool_output.impl.jac +208 -0
  78. jac_coder/util/sandbox.jac +8 -0
  79. jac_coder/util/tool_output.jac +29 -0
  80. jac_coder/walkers.jac +67 -0
  81. jac_coder-0.1.0.dist-info/METADATA +9 -0
  82. jac_coder-0.1.0.dist-info/RECORD +85 -0
  83. jac_coder-0.1.0.dist-info/WHEEL +5 -0
  84. jac_coder-0.1.0.dist-info/entry_points.txt +3 -0
  85. jac_coder-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,131 @@
1
+ # AI Integration — by llm()
2
+
3
+ ## Required Setup — byllm Import
4
+
5
+ **You MUST import byllm and create a glob model before using any `by llm()` call.**
6
+
7
+ ```jac
8
+ import from byllm.lib { Model }
9
+
10
+ glob llm: Model = Model(model_name="gpt-4o");
11
+ ```
12
+
13
+ Without this, `by llm()` will fail. The glob variable name (e.g. `llm`, `model`) is what you reference in `by llm()` or `by model()`.
14
+
15
+ ## Simple LLM Functions
16
+
17
+ The `by llm()` suffix turns a function into an LLM call. The LLM infers behavior from the function name, parameter names, types, and return type.
18
+
19
+ ```jac
20
+ import from byllm.lib { Model }
21
+
22
+ glob llm: Model = Model(model_name="gpt-4o");
23
+
24
+ # Simple classification — LLM figures it out from name + types
25
+ def classify_sentiment(text: str) -> str by llm();
26
+
27
+ # With explicit reasoning
28
+ def classify(message: str) -> str by llm(method="Reason", temperature=0.1);
29
+
30
+ # Enum return type for constrained output
31
+ enum Sentiment { POSITIVE, NEGATIVE, NEUTRAL }
32
+ def analyze(text: str) -> Sentiment by llm();
33
+ ```
34
+
35
+ ## Semantic Annotations
36
+
37
+ `sem` annotations provide system prompts for `by llm()` functions.
38
+
39
+ ```jac
40
+ # On methods
41
+ sem MyAgent.respond = """You are an expert coding agent.
42
+ Use tools to read, write, and test code.""";
43
+
44
+ # On fields (describes what the field means to the LLM)
45
+ sem ReviewResult.is_approved = "True if content meets quality standards";
46
+ ```
47
+
48
+ ## ReAct Agent with Tools
49
+
50
+ Passing `tools=` auto-triggers ReAct (reason-act) loop.
51
+
52
+ ```jac
53
+ def respond(message: str, chat_history: list[dict]) -> str by llm(
54
+ messages=chat_history,
55
+ tools=[read_file, search, bash_exec],
56
+ incl_info={"reference": knowledge_base},
57
+ max_react_iterations=10,
58
+ temperature=0.2,
59
+ stream=True
60
+ );
61
+ ```
62
+
63
+ ## LLM-Driven Graph Traversal
64
+
65
+ The LLM can decide which node to visit next:
66
+
67
+ ```jac
68
+ # LLM picks child node based on context
69
+ can route with Router entry {
70
+ visit [-->] by llm(
71
+ incl_info={"message": self.message, "context": self.context}
72
+ );
73
+ }
74
+ ```
75
+
76
+ ## Model Configuration
77
+
78
+ ```jac
79
+ import from byllm.lib { Model }
80
+
81
+ glob model: Model = Model(model_name="openai/gpt-4o-mini");
82
+
83
+ # Use specific model for a function
84
+ def summarize(text: str) -> str by model(
85
+ reason="Summarize in 2-3 sentences"
86
+ );
87
+ ```
88
+
89
+ ## Interface/Implementation with LLM
90
+
91
+ Declaration file:
92
+ ```jac
93
+ node Router {
94
+ def classify(message: str) -> str by llm(method="Reason");
95
+ }
96
+ ```
97
+
98
+ Implementation file:
99
+ ```jac
100
+ sem Router.classify = """Return exactly one label: BUILD, PLAN, or EXPLORE.""";
101
+ ```
102
+
103
+ ## Full Agent Example
104
+
105
+ ```jac
106
+ node Router {}
107
+ node QAHandler {
108
+ def respond(message: str, context: list[dict]) -> str by llm(
109
+ method="Reason", messages=context, temperature=0.3
110
+ );
111
+ can handle with process_request entry;
112
+ }
113
+
114
+ walker :pub process_request {
115
+ has message: str;
116
+ has context: list[dict] = [];
117
+
118
+ can route with Router entry {
119
+ visit [-->] by llm(
120
+ incl_info={"message": self.message}
121
+ );
122
+ }
123
+ can init_graph with Root entry {
124
+ visit [-->][?:Router] else {
125
+ router = (here ++> Router())[0];
126
+ router ++> QAHandler();
127
+ visit router;
128
+ };
129
+ }
130
+ }
131
+ ```
@@ -0,0 +1,215 @@
1
+ # Backend Development — Endpoints, Persistence, Auth
2
+
3
+ ## Function Endpoints (def:pub / def:priv)
4
+
5
+ Simple CRUD — preferred for most use cases.
6
+
7
+ ```jac
8
+ import from uuid { uuid4 }
9
+ import from datetime { datetime }
10
+
11
+ node Todo {
12
+ has id: str = "";
13
+ has title: str = "";
14
+ has completed: bool = False;
15
+ has created_at: str = "";
16
+ }
17
+
18
+ def:pub get_todos() -> list {
19
+ return [{"id": t.id, "title": t.title, "completed": t.completed}
20
+ for t in [root-->][?:Todo]];
21
+ }
22
+
23
+ def:pub add_todo(title: str) -> dict {
24
+ todo = (root ++> Todo(
25
+ id=str(uuid4()), title=title,
26
+ created_at=str(datetime.now())
27
+ ))[0];
28
+ return {"id": todo.id, "title": todo.title, "completed": False};
29
+ }
30
+
31
+ def:pub toggle_todo(id: str) -> dict {
32
+ for todo in [root-->][?:Todo] {
33
+ if todo.id == id {
34
+ todo.completed = not todo.completed;
35
+ return {"id": todo.id, "completed": todo.completed};
36
+ }
37
+ }
38
+ return {"error": "not found"};
39
+ }
40
+
41
+ def:pub delete_todo(id: str) -> dict {
42
+ for todo in [root-->][?:Todo] {
43
+ if todo.id == id {
44
+ del todo;
45
+ return {"deleted": id};
46
+ }
47
+ }
48
+ return {"error": "not found"};
49
+ }
50
+ ```
51
+
52
+ ## Walker Endpoints
53
+
54
+ For graph traversal and multi-step operations:
55
+
56
+ ```jac
57
+ walker :pub get_post_with_comments {
58
+ has post_id: str = "";
59
+
60
+ can find_post with Root entry {
61
+ for p in [-->][?:Post] {
62
+ if p.id == self.post_id { visit p; return; }
63
+ }
64
+ report {"error": "not found"};
65
+ }
66
+
67
+ can collect with Post entry {
68
+ comments = [{"id": c.id, "text": c.text}
69
+ for c in [-->][?:Comment]];
70
+ report {
71
+ "post": {"id": here.id, "title": here.title},
72
+ "comments": comments
73
+ };
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## Data Persistence
79
+
80
+ - Nodes connected to `root` **auto-persist** across requests (SQLite)
81
+ - No database setup needed — the language handles it
82
+ - `del node;` removes from graph and storage
83
+
84
+ ```jac
85
+ # Create + persist
86
+ root ++> Todo(id=str(uuid4()), title="Buy milk");
87
+
88
+ # Query all
89
+ todos = [root-->][?:Todo];
90
+
91
+ # Query with filter
92
+ found = [root-->][?:Todo](?title == "Buy milk");
93
+
94
+ # Delete
95
+ root del--> todo;
96
+ # or: del todo;
97
+ ```
98
+
99
+ ## Per-User Data Isolation (def:priv)
100
+
101
+ `:priv` endpoints require authentication. Each user gets their own isolated `root`.
102
+
103
+ ```jac
104
+ # Public — shared root, anyone can call
105
+ def:pub get_shared_data() -> list {
106
+ return [{"id": str(d.id)} for d in [root-->][?:DataNode]];
107
+ }
108
+
109
+ # Private — per-user root, requires login
110
+ def:priv get_my_tasks() -> list {
111
+ return [{"id": t.id, "title": t.title}
112
+ for t in [root-->][?:Task]];
113
+ }
114
+ ```
115
+
116
+ ## Authentication
117
+
118
+ Built-in auth functions for fullstack apps:
119
+
120
+ ```jac
121
+ cl import from "@jac/runtime" { jacLogin, jacSignup, jacLogout, jacIsLoggedIn }
122
+ ```
123
+
124
+ | Function | Returns | Purpose |
125
+ |----------|---------|---------|
126
+ | `jacLogin(username, password)` | `bool` | Log in, stores token |
127
+ | `jacSignup(username, password)` | `dict` (has `.success`) | Register user |
128
+ | `jacLogout()` | `void` | Clear auth token |
129
+ | `jacIsLoggedIn()` | `bool` | Check valid token |
130
+
131
+ ## File-Based Routing
132
+
133
+ ```
134
+ pages/
135
+ ├── layout.jac # Root layout with <Outlet />
136
+ ├── index.jac # Route: /
137
+ ├── about.jac # Route: /about
138
+ ├── users/
139
+ │ ├── index.jac # Route: /users
140
+ │ └── [id].jac # Route: /users/:id (dynamic)
141
+ ├── (public)/ # Route group (no URL segment)
142
+ │ └── login.jac # Route: /login
143
+ ├── (auth)/ # Protected group
144
+ │ ├── layout.jac # AuthGuard layout
145
+ │ └── dashboard.jac # Route: /dashboard
146
+ └── [...notFound].jac # Catch-all 404
147
+ ```
148
+
149
+ ### Layout with Outlet
150
+
151
+ ```jac
152
+ # pages/layout.jac
153
+ cl import from "@jac/runtime" { Outlet, Link }
154
+
155
+ cl {
156
+ def:pub layout() -> JsxElement {
157
+ return <>
158
+ <nav>
159
+ <Link to="/">Home</Link>
160
+ <Link to="/about">About</Link>
161
+ </nav>
162
+ <main><Outlet /></main>
163
+ </>;
164
+ }
165
+ }
166
+ ```
167
+
168
+ ### Dynamic Routes
169
+
170
+ ```jac
171
+ # pages/users/[id].jac
172
+ cl import from "@jac/runtime" { useParams }
173
+
174
+ cl {
175
+ def:pub page() -> JsxElement {
176
+ params = useParams();
177
+ return <div>User: {params.id}</div>;
178
+ }
179
+ }
180
+ ```
181
+
182
+ ### Protected Routes
183
+
184
+ ```jac
185
+ # pages/(auth)/layout.jac
186
+ cl import from "@jac/runtime" { AuthGuard, Outlet }
187
+
188
+ cl {
189
+ def:pub layout() -> JsxElement {
190
+ return <AuthGuard redirect="/login"><Outlet /></AuthGuard>;
191
+ }
192
+ }
193
+ ```
194
+
195
+ ### Navigation
196
+
197
+ ```jac
198
+ cl import from "@jac/runtime" { useNavigate, Link }
199
+
200
+ navigate = useNavigate();
201
+ navigate("/path"); # Push
202
+ navigate("/path", {"replace": True}); # Replace
203
+ navigate(-1); # Back
204
+
205
+ <Link to="/about">About</Link>
206
+ ```
207
+
208
+ ## Running a Fullstack App
209
+
210
+ ```bash
211
+ jac start --dev # Dev: Vite on :8000, API on :8001
212
+ jac start # Production: single server on :8000
213
+ ```
214
+
215
+ Visit `http://localhost:8000/docs` for Swagger UI.
@@ -0,0 +1,271 @@
1
+ # Frontend Components — .cl.jac
2
+
3
+ ## Component Definition
4
+
5
+ Components are public functions returning `JsxElement`.
6
+
7
+ ```jac
8
+ def:pub Header(props: dict) -> JsxElement {
9
+ title = props.title or "";
10
+ return (
11
+ <header className="border-b px-6 py-4">
12
+ <h1 className="text-xl font-bold">{title}</h1>
13
+ </header>
14
+ );
15
+ }
16
+ ```
17
+
18
+ Components need `def:pub` to be importable by other files.
19
+
20
+ ## State Management
21
+
22
+ `has` inside a component function auto-generates `useState`.
23
+
24
+ ```jac
25
+ def:pub Counter() -> JsxElement {
26
+ has count: int = 0;
27
+ has name: str = "";
28
+ has items: list = [];
29
+
30
+ # Assignment triggers re-render (calls setter internally)
31
+ count = count + 1;
32
+ items = items + [newItem]; # new reference = re-render
33
+ }
34
+ ```
35
+
36
+ - NEVER define `setX` yourself — it already exists from `has`
37
+ - NEVER use `.append()` — it mutates in place (no re-render)
38
+ - Always init with correct type, NEVER `None` or `any`
39
+
40
+ ## Effects (Lifecycle)
41
+
42
+ ```jac
43
+ can with entry { ... } # Mount (useEffect with [])
44
+ async can with entry { ... } # Async mount
45
+ can with [dep1, dep2] entry { ... } # Watch dependencies
46
+ can with (a, b) entry { ... } # Also dependency watch
47
+ can with exit { ... } # Cleanup/unmount
48
+ ```
49
+
50
+ NEVER use `useEffect(lambda...)` — that is OLD syntax.
51
+
52
+ ## Event Handlers
53
+
54
+ ALL handlers must be named `def` functions defined BEFORE `return`. NEVER use lambda or inline def in JSX.
55
+
56
+ ```jac
57
+ def:pub TodoList() -> JsxElement {
58
+ has inputValue: str = "";
59
+
60
+ # Define handler above return
61
+ def handle_input(e: any) -> None {
62
+ inputValue = e.target.value;
63
+ }
64
+
65
+ def handle_submit() -> None {
66
+ if inputValue { add_item(inputValue); inputValue = ""; }
67
+ }
68
+
69
+ # Pass by name
70
+ return <div>
71
+ <input value={inputValue} onChange={handle_input} />
72
+ <button onClick={handle_submit}>Add</button>
73
+ </div>;
74
+ }
75
+ ```
76
+
77
+ ## List Rendering
78
+
79
+ Use list comprehension, NEVER `.map()`.
80
+
81
+ ```jac
82
+ # Render list
83
+ {[<TodoItem key={item["id"]} data={item} /> for item in items]}
84
+
85
+ # With filter
86
+ {[<Tag key={t} label={t} /> for t in tags if t != "hidden"]}
87
+
88
+ # Add to list (new reference)
89
+ items = items + [newItem];
90
+
91
+ # Remove from list
92
+ items = [item for item in items if item["id"] != targetId];
93
+ ```
94
+
95
+ ## Conditional Rendering
96
+
97
+ ```jac
98
+ {showSidebar and (<Sidebar items={items} />)}
99
+ {isActive and (<ActiveView />) or (<InactiveView />)}
100
+ {(not loading) and (<Content />)}
101
+ ```
102
+
103
+ ## Custom Hooks
104
+
105
+ ```jac
106
+ def:pub useCounter(initial: int = 0) -> dict {
107
+ has count: int = initial;
108
+ def increment() -> None { count = count + 1; }
109
+ def decrement() -> None { count = count - 1; }
110
+ return {"count": count, "increment": increment, "decrement": decrement};
111
+ }
112
+ ```
113
+
114
+ ## Data Hook Pattern (sv import + state)
115
+
116
+ ```jac
117
+ # hooks/useTodos.cl.jac
118
+ sv import from ..main { get_todos, add_todo, delete_todo }
119
+
120
+ def:pub useTodos() -> dict {
121
+ has todos: list = [];
122
+ has loading: bool = True;
123
+ has error: str = "";
124
+
125
+ async can with entry {
126
+ try {
127
+ result = await get_todos();
128
+ todos = result or [];
129
+ } except Exception as e {
130
+ error = str(e);
131
+ }
132
+ loading = False;
133
+ }
134
+
135
+ async def handleAdd(title: str) -> None {
136
+ if not title { return; }
137
+ result = await add_todo(title); # positional args only
138
+ if result and not result.error {
139
+ todos = todos + [result];
140
+ }
141
+ }
142
+
143
+ async def handleDelete(id: str) -> None {
144
+ result = await delete_todo(id); # positional args only
145
+ if result and not result.error {
146
+ todos = [t for t in todos if t["id"] != id];
147
+ }
148
+ }
149
+
150
+ return {
151
+ "todos": todos, "loading": loading, "error": error,
152
+ "handleAdd": handleAdd, "handleDelete": handleDelete
153
+ };
154
+ }
155
+ ```
156
+
157
+ ## Frontend Import Rules
158
+
159
+ ```jac
160
+ # Same directory (.cl.jac to .cl.jac) — WITH quotes, dots not slashes
161
+ import from ".Header" { Header }
162
+
163
+ # Parent directory — WITH quotes
164
+ import from "..hooks.useTodos" { useTodos }
165
+
166
+ # Two levels up — WITH quotes
167
+ import from "...lib.utils" { cn }
168
+
169
+ # Server calls — sv import, NO quotes
170
+ sv import from ..main { get_todos, add_todo }
171
+
172
+ # NPM packages — WITH quotes
173
+ import from "clsx" { cn }
174
+
175
+ # Runtime — cl import, WITH quotes
176
+ cl import from "@jac/runtime" { jacLogin, Outlet }
177
+
178
+ # CSS — WITH quotes
179
+ import "..styles.global.css";
180
+ ```
181
+
182
+ **Dot levels:** `.` = same dir, `..` = parent, `...` = 2 up
183
+
184
+ ## Calling Backend Walkers
185
+
186
+ ```jac
187
+ sv import from ..main { get_post_details }
188
+
189
+ async def loadDetails() -> None {
190
+ result = root spawn get_post_details(post_id=postId);
191
+ if result and result.reports and len(result.reports) > 0 {
192
+ data = result.reports[0];
193
+ }
194
+ }
195
+ ```
196
+
197
+ ## Primitive Idioms (Jac, NOT JavaScript)
198
+
199
+ | JS (WRONG) | Jac (RIGHT) |
200
+ |---|---|
201
+ | `console.log(x)` | `print(x)` |
202
+ | `String(x)` / `.toString()` | `str(x)` |
203
+ | `parseInt(x)` | `int(x)` |
204
+ | `x.length` | `len(x)` |
205
+ | `.toLowerCase()` | `.lower()` |
206
+ | `.toUpperCase()` | `.upper()` |
207
+ | `.trim()` | `.strip()` |
208
+ | `.indexOf(sub)` | `.find(sub)` |
209
+
210
+ ## Inline Styles
211
+
212
+ ```jac
213
+ <div style={{"display": "flex", "gap": "8px", "backgroundColor": "#f0f0f0"}} />
214
+ ```
215
+
216
+ ## Service Layer Pattern (for walker calls)
217
+
218
+ ```jac
219
+ # services/apiService.cl.jac
220
+ sv import from ..main { create_item, list_items }
221
+
222
+ async def:pub createItem(title: str) -> any {
223
+ try {
224
+ response = root spawn create_item(title=title);
225
+ result = response.reports[len(response.reports) - 1]
226
+ if response.reports and len(response.reports) > 0 else {};
227
+ return {"success": True, "item": result};
228
+ } except Exception as e {
229
+ return {"success": False, "error": str(e)};
230
+ }
231
+ }
232
+ ```
233
+
234
+ ## @jac/runtime — Available Symbols
235
+
236
+ Use `cl import from "@jac/runtime"` (WITH quotes, WITH `cl` prefix) to import runtime utilities.
237
+
238
+ ### Auth
239
+ ```jac
240
+ cl import from "@jac/runtime" { jacLogin, jacSignup, jacLogout, jacIsLoggedIn }
241
+
242
+ # Usage
243
+ success = await jacLogin(username, password); # positional args only
244
+ success = await jacSignup(username, password);
245
+ jacLogout();
246
+ is_auth = jacIsLoggedIn(); # returns bool
247
+ ```
248
+
249
+ ### Routing
250
+ ```jac
251
+ cl import from "@jac/runtime" { Link, Navigate, useNavigate, Outlet }
252
+
253
+ # Link — declarative navigation
254
+ <Link to="/dashboard">Go to Dashboard</Link>
255
+
256
+ # Navigate — redirect component
257
+ if not jacIsLoggedIn() { return <Navigate to="/login" />; }
258
+
259
+ # useNavigate — programmatic navigation
260
+ navigate = useNavigate();
261
+ def handle_go() -> None { navigate("/dashboard"); }
262
+
263
+ # Outlet — renders child routes (file-based routing)
264
+ return <div><Outlet /></div>;
265
+ ```
266
+
267
+ ### IMPORTANT
268
+ - ALWAYS use `cl import` prefix (not plain `import`)
269
+ - ALWAYS use quotes: `"@jac/runtime"` (it's an npm-style path)
270
+ - NEVER use dots: `@jac.runtime` ← WRONG
271
+ - Auth functions use POSITIONAL args (kwargs broken in .cl.jac)