create-nola-lang 0.1.2 → 0.1.4
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.
- package/README.md +3 -2
- package/dist/agents.d.ts +27 -2
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +136 -67
- package/dist/agents.js.map +1 -1
- package/dist/{chunk-QTJ64H2Y.js → chunk-VZM5EYBG.js} +138 -64
- package/dist/flow.d.ts +3 -0
- package/dist/flow.d.ts.map +1 -1
- package/dist/flow.js +9 -7
- package/dist/flow.js.map +1 -1
- package/dist/index.js +5 -3
- package/dist/main.js +1 -1
- package/dist/package-manager.d.ts +17 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +8 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/skill-install.d.ts +2 -0
- package/dist/skill-install.d.ts.map +1 -1
- package/dist/skill-install.js +6 -2
- package/dist/skill-install.js.map +1 -1
- package/package.json +7 -5
- package/skills/nola/SKILL.md +75 -0
- package/skills/nola/references/config.md +227 -0
- package/skills/nola/references/patterns.md +277 -0
- package/skills/nola/references/pitfalls.md +289 -0
- package/skills/nola/references/syntax.md +377 -0
- package/templates/starter/_gitignore +2 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# Nola syntax reference
|
|
2
|
+
|
|
3
|
+
Everything valid in TypeScript is valid in a `.tsi` file. This file documents
|
|
4
|
+
only the additions. `ask` and `with` are reserved words in `.tsi` (`ask` stays
|
|
5
|
+
legal as a member/property name); `infer` is a keyword only directly before
|
|
6
|
+
`function` at statement or export position, so `T extends infer U` in a
|
|
7
|
+
conditional type is untouched.
|
|
8
|
+
|
|
9
|
+
## `infer function`
|
|
10
|
+
|
|
11
|
+
An `infer function` declares an LLM-backed function. Calling one runs NOTHING:
|
|
12
|
+
it returns a lazy, thenable `Intent<T>`. The work happens when the intent is
|
|
13
|
+
resolved — with `ask` inside another infer function, or with `await` from
|
|
14
|
+
plain TS.
|
|
15
|
+
|
|
16
|
+
```tsi
|
|
17
|
+
// plain
|
|
18
|
+
infer function summarize(.text: string) {
|
|
19
|
+
return ask ..`a one-sentence summary`<string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// exported
|
|
23
|
+
export infer function classify(.message: string) {
|
|
24
|
+
return ask ..`the category of the message`<string>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// with an instruction marker between the name and the parameter list
|
|
28
|
+
export infer function triage`triage the ticket like a support lead`(.ticket: string) {
|
|
29
|
+
return ask ..`the severity: low, medium or high`<"low" | "medium" | "high">;
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Rules:
|
|
34
|
+
|
|
35
|
+
- Top-level function declarations only. `infer` on a method, arrow function,
|
|
36
|
+
or function expression is a "reserved for a future Nola version" error.
|
|
37
|
+
- `async infer function` is a parse error — an infer function is never `async`
|
|
38
|
+
in source; it is implicitly awaitable through `Intent`.
|
|
39
|
+
- The instruction marker is a template literal. `${expr}` holes interpolate
|
|
40
|
+
lexical values into the instruction; a `${.member}` hole makes the marker a
|
|
41
|
+
prompt TEMPLATE for the function's CONTEXT block (see "Prompt templates").
|
|
42
|
+
- `export default infer function` does NOT parse. Export by name
|
|
43
|
+
(`export infer function f(...)`) and let consumers import the name.
|
|
44
|
+
- `ask` is legal only DIRECTLY inside an infer function body — not at module
|
|
45
|
+
level, not inside a nested closure (NOLA2001).
|
|
46
|
+
- `await` IS legal inside the body, for ordinary promises (fetch, DB, any
|
|
47
|
+
library):
|
|
48
|
+
|
|
49
|
+
```tsi
|
|
50
|
+
export infer function enrich(.handle: string, fetchProfile: (h: string) => Promise<string>) {
|
|
51
|
+
const profile = await fetchProfile(handle); // ordinary promise
|
|
52
|
+
return ask ..`the person's job title from: ${profile}`<string>;
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Return type annotation
|
|
57
|
+
|
|
58
|
+
Leave the return type off and let it infer — that is what every example in
|
|
59
|
+
this repo does. When you do annotate, the annotation is `Intent<T>` (the body
|
|
60
|
+
returns `T`, the way an `async` function body returns `T` under `Promise<T>`):
|
|
61
|
+
|
|
62
|
+
```tsi
|
|
63
|
+
import type { Intent } from "@nola-lang/runtime";
|
|
64
|
+
|
|
65
|
+
interface User {
|
|
66
|
+
name: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export infer function getUser(.message: string): Intent<User> {
|
|
70
|
+
const user = ask ..`the user described in the message`<User>;
|
|
71
|
+
return user;
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Do NOT annotate it `Promise<T>`: `Intent<T>` is `PromiseLike<T>`, not a
|
|
76
|
+
`Promise`, so `nola check` reports TS2739 (missing `catch`, `finally`,
|
|
77
|
+
`[Symbol.toStringTag]`).
|
|
78
|
+
|
|
79
|
+
## `.` contextual parameters
|
|
80
|
+
|
|
81
|
+
A parameter prefixed with ONE dot is a CONTEXT parameter: its name, type and
|
|
82
|
+
runtime VALUE are composed into the prompt of every `ask` in that invocation.
|
|
83
|
+
A plain parameter is an ordinary JS argument — its name and type reach the
|
|
84
|
+
LLM, its value does not. Mnemonic: one dot IN (`.name`), two dots OUT
|
|
85
|
+
(`` ..`prompt` ``). Writing `..name` on a parameter is NOLA1013.
|
|
86
|
+
|
|
87
|
+
```tsi
|
|
88
|
+
export type Issue = { id: string; description: string };
|
|
89
|
+
|
|
90
|
+
// `issue` is visible to the LLM; `fallback` is a normal JS value only.
|
|
91
|
+
export infer function classifyIssue(.issue: Issue, fallback: string) {
|
|
92
|
+
const kind = ask ..`the kind of this issue`<string>;
|
|
93
|
+
return kind || fallback;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- `.` is legal ONLY on infer-function parameters. On any other function it is
|
|
98
|
+
NOLA1010.
|
|
99
|
+
- A contextual parameter's TYPE must be derivable to an inference schema:
|
|
100
|
+
strings, numbers, booleans, `Date`, arrays, plain object/interface/type-alias
|
|
101
|
+
shapes, string-literal unions, string enums, and same-file or imported
|
|
102
|
+
references to those. `Map`, `Set` and other ambient lib types are NOT
|
|
103
|
+
derivable and raise NOLA2008 under the default policy.
|
|
104
|
+
- Several contextual parameters are fine; they compose into one context block:
|
|
105
|
+
|
|
106
|
+
```tsi
|
|
107
|
+
export infer function nextQuery(.question: string, .notes: string[]) {
|
|
108
|
+
return ask ..`the single best search query to advance the research`<string>;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
- `const .x = …` (a contextual BINDING inside the body) is reserved for a
|
|
112
|
+
future Nola version — NOLA1014 today.
|
|
113
|
+
|
|
114
|
+
## Extractors — `` ..`instruction`<T> ``
|
|
115
|
+
|
|
116
|
+
An extractor is the request itself: instruction text in backticks plus an
|
|
117
|
+
optional type argument.
|
|
118
|
+
|
|
119
|
+
```tsi
|
|
120
|
+
export infer function parse(.doc: string) {
|
|
121
|
+
const id = ask ..`the ticket id`<string>; // typed
|
|
122
|
+
const count = ask ..`how many line items`<number>;
|
|
123
|
+
const free = ask ..`think step by step about the document`; // untyped
|
|
124
|
+
return { id, count, free };
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- `${expr}` interpolation is legal inside the backticks and is evaluated at
|
|
129
|
+
intent-construction time. Strings splice as-is; anything else is
|
|
130
|
+
JSON-stringified. A hole starting with a single dot (`${.type}`) is NOT a
|
|
131
|
+
lexical value — it reads the extractor's prompt scope and turns the
|
|
132
|
+
backticks into a prompt template (see "Prompt templates"):
|
|
133
|
+
|
|
134
|
+
```tsi
|
|
135
|
+
interface Person {
|
|
136
|
+
name: string;
|
|
137
|
+
age: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export infer function lookup(text: string) {
|
|
141
|
+
return ask ..`the person described in: ${text}`<Person>;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- With no `<T>`, the extractor asks for free text: the wire schema is a plain
|
|
146
|
+
string and the static TS type is `any`. Give every extractor an explicit
|
|
147
|
+
`<T>` unless you deliberately want unconstrained prose.
|
|
148
|
+
- `<T>` accepts scalars, `Date`, arrays, inline object literals, same-file
|
|
149
|
+
and imported non-generic aliases/interfaces, string-literal unions and
|
|
150
|
+
string enums. JSDoc comments on members become schema descriptions:
|
|
151
|
+
|
|
152
|
+
```tsi
|
|
153
|
+
export interface Conclusion {
|
|
154
|
+
answer: string;
|
|
155
|
+
/** the collected notes that directly support the answer */
|
|
156
|
+
evidence: string[];
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- An extractor may be CONSTRUCTED anywhere in a `.tsi` file, module level
|
|
161
|
+
included — construction needs no context. Only `ask` is position-restricted:
|
|
162
|
+
|
|
163
|
+
```tsi
|
|
164
|
+
export const nameIntent = ..`the user's full name`<string>; // legal, inert
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## The `ask` operator
|
|
168
|
+
|
|
169
|
+
`ask` is a unary prefix operator with `await`'s precedence. It resolves any
|
|
170
|
+
`Askable` — an extractor, a call intent, or the `Intent` returned by calling
|
|
171
|
+
an infer function — to its value.
|
|
172
|
+
|
|
173
|
+
```tsi
|
|
174
|
+
import { getUserById } from "./users.tsi";
|
|
175
|
+
|
|
176
|
+
type User = { name: string };
|
|
177
|
+
|
|
178
|
+
export infer function report(.text: string) {
|
|
179
|
+
const user = ask ..`the user named in the text`<User>; // extractor
|
|
180
|
+
const record = ask getUserById(user.name); // another infer function
|
|
181
|
+
return record;
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### `ask with <name>` — pin one ask to a provider
|
|
186
|
+
|
|
187
|
+
```tsi
|
|
188
|
+
export infer function summarize(.text: string) {
|
|
189
|
+
const draft = ask with fast ..`a rough summary`<string>;
|
|
190
|
+
const final = ask with careful ..`a polished summary of: ${draft}`<string>;
|
|
191
|
+
return final;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
- `<name>` must be a STATIC identifier naming a key of the `providers` map in
|
|
196
|
+
`nola.config.ts`. An extractor, a parenthesized expression, a string literal
|
|
197
|
+
or anything else after `with` is NOLA1009 — use `.withProvider(...)` for a
|
|
198
|
+
dynamic provider.
|
|
199
|
+
- The name is matched against the config at ask time, not compile time; an
|
|
200
|
+
unknown name is a runtime `NolaConfigError` (NOLA3004).
|
|
201
|
+
- `ask without` is a plain ask of the identifier `without`, not a pin.
|
|
202
|
+
|
|
203
|
+
## Call intents
|
|
204
|
+
|
|
205
|
+
A call intent lets the LLM fill some of a function's arguments, then calls the
|
|
206
|
+
function with them. All slots of one call intent resolve in ONE provider call.
|
|
207
|
+
|
|
208
|
+
Three spellings:
|
|
209
|
+
|
|
210
|
+
```tsi
|
|
211
|
+
declare function createTicket(title: string, priority: number): Promise<string>;
|
|
212
|
+
|
|
213
|
+
export infer function file(.request: string) {
|
|
214
|
+
// 1. sigil-less — a plain call whose arguments contain an extractor
|
|
215
|
+
const a = ask createTicket(..`a short ticket title`<string>, 2);
|
|
216
|
+
|
|
217
|
+
// 2. empty marker — identical lowering; the only spelling for a call
|
|
218
|
+
// intent whose arguments are all plain
|
|
219
|
+
const b = ask createTicket``("fallback title", 3);
|
|
220
|
+
|
|
221
|
+
// 3. hint marker — the ONLY carrier of instruction text for the call
|
|
222
|
+
const c = ask createTicket`file the ticket the customer asked for`(
|
|
223
|
+
..`a short ticket title`<string>,
|
|
224
|
+
..`priority 1-5, 1 is most urgent`<number>,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
return { a, b, c };
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Detection rule for the sigil-less form — BOTH must hold:
|
|
232
|
+
|
|
233
|
+
- the callee is an `Identifier` or a `MemberExpression` (any nesting, computed
|
|
234
|
+
included), and
|
|
235
|
+
- at least one well-formed extractor appears in a slot position: a direct
|
|
236
|
+
argument, or nested at any depth inside plain object/array literals.
|
|
237
|
+
|
|
238
|
+
```tsi
|
|
239
|
+
declare const api: { save(order: { qty: number; note: string }): Promise<string> };
|
|
240
|
+
|
|
241
|
+
export infer function place(.request: string) {
|
|
242
|
+
// member callee + extractor nested in an object literal → call intent
|
|
243
|
+
return ask api.save({ qty: 1, note: ..`a one-line note for the warehouse`<string> });
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
These stay PLAIN calls (the extractor is just a value argument): an extractor
|
|
248
|
+
inside a ternary, logical expression, spread element or template substitution;
|
|
249
|
+
a nested call (in `` outer(inner(..`x`<T>)) `` the INNER call is the intent and
|
|
250
|
+
`outer` receives an `Askable`); `new Foo(...)`, `super(...)`, `import(...)`,
|
|
251
|
+
optional calls (`fn?.(...)`, `a?.b(...)`); and exotic callees (`getFn()(...)`,
|
|
252
|
+
IIFEs) — use the marker form if you want a call intent on one of those.
|
|
253
|
+
|
|
254
|
+
Parenthesizing an extractor does NOT opt out. To pass an intent as a plain
|
|
255
|
+
value, bind it to a variable first:
|
|
256
|
+
|
|
257
|
+
```tsi
|
|
258
|
+
const i = ..`a short title`<string>;
|
|
259
|
+
helper(i); // plain call — helper receives the Askable
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Every extractor used as a call-intent slot must carry an explicit `<T>`
|
|
263
|
+
(NOLA2004). The bare derive-all form `fn(..)` is reserved (NOLA1004).
|
|
264
|
+
|
|
265
|
+
### Result of a call intent — async callees are awaited
|
|
266
|
+
|
|
267
|
+
`ask fn(...)` yields the callee's SETTLED value, exactly like `await fn(...)`
|
|
268
|
+
would: if the function returns a promise (or any thenable), the intent awaits
|
|
269
|
+
it before resolving. Its static type is `Awaited<ReturnType<typeof fn>>`. Never
|
|
270
|
+
write `await ask fn(...)` — the extra `await` is a no-op.
|
|
271
|
+
|
|
272
|
+
```tsi
|
|
273
|
+
declare function createTicket(title: string, priority: number): Promise<string>;
|
|
274
|
+
|
|
275
|
+
export infer function file(.request: string) {
|
|
276
|
+
const id = ask createTicket(..`a short ticket title`<string>, 2); // id: string, not Promise<string>
|
|
277
|
+
return id;
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Two consequences to keep in mind:
|
|
282
|
+
|
|
283
|
+
- The callee runs INSIDE the ask, so a rejected promise fails the ask at the
|
|
284
|
+
call site (NolaResolutionError with the intent's location) — and
|
|
285
|
+
`.withRetry(n)` re-runs the WHOLE ask, including the callee. Do not put
|
|
286
|
+
`.withRetry` on a call intent whose target is not idempotent.
|
|
287
|
+
- The invocation timeout (`ask.timeoutMs` / `.withTimeout`) bounds provider
|
|
288
|
+
round trips only. Once the arguments are filled, the callee's own promise
|
|
289
|
+
runs to completion, the same as a plain `await fn()` in your code.
|
|
290
|
+
|
|
291
|
+
## Prompt templates — `${.member}`
|
|
292
|
+
|
|
293
|
+
Every instruction literal (infer-function marker, extractor prompt, call-intent
|
|
294
|
+
hint) can act as a TEMPLATE for the prompt block that intent contributes. One
|
|
295
|
+
rule: a substitution hole whose expression starts with a single dot reads the
|
|
296
|
+
intent's prompt scope; every other hole is ordinary lexical JavaScript.
|
|
297
|
+
|
|
298
|
+
```tsi
|
|
299
|
+
infer function analyze`${.default}
|
|
300
|
+
Rules: answer only from the arguments above; never invent ids.`(.ticket: Ticket) {
|
|
301
|
+
const id = ask ..`ticket id, comply with ${.type}`<string>;
|
|
302
|
+
return id;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// A full custom CONTEXT block — everything after the dot is plain TypeScript
|
|
306
|
+
infer function triage`
|
|
307
|
+
CONTEXT — inside ${.signature}, ${.file}
|
|
308
|
+
${.args.map(a => `- ${a.name} (${a.type}): ${JSON.stringify(a.value)}`)}
|
|
309
|
+
|
|
310
|
+
TASK
|
|
311
|
+
${.next}
|
|
312
|
+
`(.ticket: string) { … }
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
- Override rule (static): a literal with at least ONE `${.x}` hole is a
|
|
316
|
+
template — its rendered text REPLACES that intent's block (CONTEXT for an
|
|
317
|
+
infer function, TASK for an extractor / call hint). A literal with no scope
|
|
318
|
+
hole is an instruction, exactly as before (`Purpose:` / `<request>` inside
|
|
319
|
+
the built-in block), even when it has lexical `${}` holes.
|
|
320
|
+
- Function scope (`FunctionPromptScope`): `.fn`, `.signature`, `.file`,
|
|
321
|
+
`.args[]` (`name`, `type` — native type text, `value`, `contextual`),
|
|
322
|
+
`.nested`, `.hasContext`, `.default` (the built-in CONTEXT block, no
|
|
323
|
+
Purpose line), `.next` (the rest of the prompt: callee blocks + TASK).
|
|
324
|
+
- Extractor / call-hint scope (`ExtractPromptScope`): `.type` (native type
|
|
325
|
+
text of the target), `.schema` (the JSON Schema, serialized),
|
|
326
|
+
`.hasContext`, `.default` (the built-in TASK block), `.format` (the JSON
|
|
327
|
+
response rules).
|
|
328
|
+
- Safe by default: a function template that never reads `.next` gets the
|
|
329
|
+
rest of the prompt appended after it; an extractor template that never
|
|
330
|
+
reads `.format` gets the JSON response rules appended. Read them only to
|
|
331
|
+
choose WHERE they go (wrapping). `.next`/`.default`/`.format` are
|
|
332
|
+
getters, memoized — reading twice does not compose twice.
|
|
333
|
+
- Rendering: arrays render one item per line (no `.join` needed),
|
|
334
|
+
`undefined`/`null` render as nothing, objects as JSON, `Date` as ISO.
|
|
335
|
+
Templates render when the ask composes its prompt — lexical values inside
|
|
336
|
+
a TEMPLATE are read then, not at construction (the only observable
|
|
337
|
+
difference from an instruction's eager `${}`).
|
|
338
|
+
- Nested holes follow the same rule: `${.file}` inside a `.map` callback's
|
|
339
|
+
own template literal still reads the scope. Keyword members work
|
|
340
|
+
(`${.default}`).
|
|
341
|
+
- Editor: completion, hover and precise TS errors work inside the backticks
|
|
342
|
+
(an unknown member is a TS2339 at the member).
|
|
343
|
+
- Errors: `${.x}` in a template literal that is not a Nola instruction is
|
|
344
|
+
NOLA2009; a Nola construct (`..`, call intent, `ask`) inside a marker /
|
|
345
|
+
call-hint hole is NOLA2010; a template that throws or renders empty fails
|
|
346
|
+
the ask with NOLA3014 (definitive).
|
|
347
|
+
|
|
348
|
+
## Intent methods
|
|
349
|
+
|
|
350
|
+
Every intent (extractor, call intent, infer-function result) accepts:
|
|
351
|
+
|
|
352
|
+
```tsi
|
|
353
|
+
export infer function tuned(.text: string) {
|
|
354
|
+
const a = ask (..`the title`<string>).withRetry(2);
|
|
355
|
+
const b = ask (..`the body`<string>).withProvider("careful");
|
|
356
|
+
const c = ask (..`a creative tagline`<string>).withParams({ temperature: 0.9, maxOutputTokens: 200 });
|
|
357
|
+
return { a, b, c };
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
- `.withRetry(n)` — `n` extra whole-ask attempts, flat, no backoff.
|
|
362
|
+
- `.withProvider(nameOrProvider)` — the dynamic form of `ask with`.
|
|
363
|
+
- `.withParams({ temperature, maxOutputTokens, providerOptions })` — wire knobs,
|
|
364
|
+
merged per field with anything already set.
|
|
365
|
+
|
|
366
|
+
Two more exist ONLY on the `Intent` an infer function returns (they act when
|
|
367
|
+
the intent roots an invocation), and are typically used from plain TS:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
import { extractPerson } from "./person.tsi";
|
|
371
|
+
|
|
372
|
+
const person = await extractPerson(text).withTimeout(30_000);
|
|
373
|
+
const loose = await extractPerson(text).detached(); // do not inherit the caller frame's context
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
All of these CLONE the intent — the original stays unstarted, and an intent
|
|
377
|
+
resolves at most once.
|