create-nola-lang 0.1.13 → 0.1.15

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 (54) hide show
  1. package/README.md +8 -3
  2. package/dist/add.d.ts +3 -0
  3. package/dist/add.js +2 -1
  4. package/dist/checkout.js +1 -0
  5. package/dist/{chunk-LUSVIOAN.js → chunk-UTVGG44K.js} +124 -95
  6. package/dist/examples.d.ts +4 -2
  7. package/dist/examples.js +4 -2
  8. package/dist/flow.d.ts +20 -3
  9. package/dist/flow.js +49 -17
  10. package/dist/github.d.ts +1 -1
  11. package/dist/github.js +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/launch.d.ts +2 -2
  14. package/dist/launch.js +1 -1
  15. package/dist/main.js +1 -1
  16. package/dist/node-version.d.ts +3 -2
  17. package/dist/node-version.js +4 -3
  18. package/dist/package-manager.d.ts +5 -0
  19. package/dist/package-manager.js +7 -0
  20. package/dist/providers.d.ts +1 -1
  21. package/dist/providers.js +1 -1
  22. package/dist/registry.d.ts +21 -12
  23. package/dist/registry.js +27 -5
  24. package/dist/scaffold.d.ts +36 -16
  25. package/dist/scaffold.js +96 -116
  26. package/package.json +1 -1
  27. package/skills/nola/SKILL.md +72 -17
  28. package/skills/nola/references/config.md +35 -6
  29. package/skills/nola/references/patterns.md +129 -31
  30. package/skills/nola/references/pitfalls.md +318 -56
  31. package/skills/nola/references/syntax.md +345 -121
  32. package/templates/empty/package.json +5 -5
  33. package/templates/empty/src/main.tsi +8 -0
  34. package/templates/empty/src/main.ts +0 -5
  35. package/templates/feature-extraction/README.md +0 -20
  36. package/templates/feature-extraction/nola.config.ts +0 -14
  37. package/templates/feature-extraction/nola.replay.jsonl +0 -2
  38. package/templates/feature-extraction/package.json +0 -22
  39. package/templates/feature-extraction/src/main.tsi +0 -22
  40. package/templates/feature-extraction/tsconfig.json +0 -12
  41. package/templates/function-calling/README.md +0 -21
  42. package/templates/function-calling/nola.config.ts +0 -14
  43. package/templates/function-calling/nola.replay.jsonl +0 -1
  44. package/templates/function-calling/package.json +0 -22
  45. package/templates/function-calling/src/main.tsi +0 -12
  46. package/templates/function-calling/src/tickets.ts +0 -17
  47. package/templates/function-calling/tsconfig.json +0 -12
  48. package/templates/typescript-interop/README.md +0 -15
  49. package/templates/typescript-interop/nola.config.ts +0 -14
  50. package/templates/typescript-interop/nola.replay.jsonl +0 -1
  51. package/templates/typescript-interop/package.json +0 -22
  52. package/templates/typescript-interop/src/main.ts +0 -8
  53. package/templates/typescript-interop/src/person.tsi +0 -11
  54. package/templates/typescript-interop/tsconfig.json +0 -12
@@ -3,8 +3,8 @@
3
3
  ## The feature-extraction project: one .tsi file is the program
4
4
 
5
5
  The smallest Nola program is a single `.tsi` file run directly — `ask` is
6
- legal at the top level, a bare template literal as the FIRST statement is
7
- the instruction for the whole file, and `const .x` bindings are context the
6
+ legal at the top level, a bare template literal on its own line is a context
7
+ statement every ask below it sees, and `const .x` bindings are context the
8
8
  model sees at every ask that follows them (`npm create nola` scaffolds this
9
9
  shape as `feature-extraction`):
10
10
 
@@ -21,17 +21,17 @@ interface Person {
21
21
 
22
22
  const .message = "Alice Smith, 32, is a staff engineer at Acme Corp working on distributed systems.";
23
23
 
24
- const person = ask `the person described in the text`<Person>;
24
+ const person = ask `the person described in the text`: Person;
25
25
 
26
26
  // declared after the first ask, so only the second ask sees it — one answer feeds the next
27
27
  const .role = person.job;
28
- const seniority = ask `the seniority level the role implies`<"junior" | "mid" | "senior" | "staff">;
28
+ const seniority = ask `the seniority level the role implies`: "junior" | "mid" | "senior" | "staff";
29
29
 
30
30
  console.log(JSON.stringify({ ...person, seniority }));
31
31
  ```
32
32
 
33
- Rules that matter here: the instruction literal must be the very first
34
- statement (a comment before it is fine, an `import` or a type is not); a
33
+ Rules that matter here: a context statement applies to the asks written
34
+ after it, wherever it stands (an `import` or a type before it is fine); a
35
35
  `.` binding is visible to the asks declared after it in the same or an
36
36
  enclosing block, never to its own initializer; `ask` at the top level is
37
37
  legal in the module body and top-level blocks/loops, not inside a plain
@@ -63,16 +63,19 @@ import { createTicket } from "./tickets.js";
63
63
 
64
64
  const .message = "Hi, I can't log in since this morning and I have a customer demo in an hour — please help!";
65
65
 
66
- const ticket = ask createTicket(..`a short ticket title`<string>, ..`priority 1-5, where 1 is most urgent`<number>);
66
+ const ticket = ask createTicket(
67
+ `a short ticket title`: string,
68
+ `priority 1-5, where 1 is most urgent`: number
69
+ );
67
70
 
68
71
  console.log(JSON.stringify(ticket));
69
72
  ```
70
73
 
71
74
  `ask` yields the callee's SETTLED value — a `Ticket`, not a `Promise<Ticket>`.
72
75
  The import uses the NodeNext `./tickets.js` specifier for the on-disk
73
- `tickets.ts`. Note there is no first-line instruction here: an `import` is a
74
- statement, so a template literal placed after it is not the file's first
75
- statement and would be a no-op.
76
+ `tickets.ts`. Note there is no context statement here: a template literal on
77
+ its own line after the `import` would be one, and every ask below it would
78
+ see it.
76
79
 
77
80
  ## The typescript-interop project, end to end
78
81
 
@@ -102,7 +105,7 @@ export interface Person {
102
105
  }
103
106
 
104
107
  export infer function extractPerson(.message: string) {
105
- const person = ask `the person described in the text`<Person>;
108
+ const person = ask `the person described in the text`: Person;
106
109
  return person;
107
110
  }
108
111
  ```
@@ -129,8 +132,8 @@ nola build # dist/ — plain JS + source maps + .d.ts
129
132
 
130
133
  ## Composing several asks in one invocation
131
134
 
132
- Every `ask` in one invocation shares that invocation's context — the `..`
133
- contextual parameters and the function's instruction marker. That is what lets
135
+ Every `ask` in one invocation shares that invocation's context — the `.`
136
+ contextual parameters and the function's context statements. That is what lets
134
137
  you split one big prompt into several small, individually-typed asks instead of
135
138
  demanding everything at once.
136
139
 
@@ -145,7 +148,7 @@ export infer function solve(.problem: string) {
145
148
  // other's answers automatically — the reasoning is handed to the second ask
146
149
  // explicitly through `${}`.
147
150
  const reasoning = ask `think step by step about the problem before answering`;
148
- const answer = ask `the final numeric answer, given this reasoning: ${reasoning}`<number>;
151
+ const answer = ask `the final numeric answer, given this reasoning: ${reasoning}`: number;
149
152
  return { reasoning, answer };
150
153
  }
151
154
  ```
@@ -158,8 +161,8 @@ later prompt:
158
161
  export type Category = "billing" | "refund" | "fraud" | "other";
159
162
 
160
163
  export infer function classifyMessage(.message: string) {
161
- const category = ask `the category of the customer message`<Category>;
162
- const urgent = ask `does the message need urgent attention`<"yes" | "no">;
164
+ const category = ask `the category of the customer message`: Category;
165
+ const urgent = ask `does the message need urgent attention`: "yes" | "no";
163
166
 
164
167
  // plain TS from here on
165
168
  if (category === "fraud") return { category, urgent: true, escalate: true };
@@ -178,11 +181,11 @@ export interface Conclusion {
178
181
  }
179
182
 
180
183
  export infer function nextQuery(.question: string, .notes: string[]) {
181
- return ask `the single best search query to advance the research; keywords only`<string>;
184
+ return ask `the single best search query to advance the research; keywords only`: string;
182
185
  }
183
186
 
184
187
  export infer function conclude(.question: string, .notes: string[]) {
185
- return ask `answer the research question using only the collected notes`<Conclusion>;
188
+ return ask `answer the research question using only the collected notes`: Conclusion;
186
189
  }
187
190
  ```
188
191
 
@@ -228,7 +231,7 @@ import { createTicket } from "./tickets.js";
228
231
  export infer function fileTicket(.request: string) {
229
232
  // Sigil-less: the extractor argument makes this call an intent. `2` is a
230
233
  // plain argument and is passed through untouched.
231
- const id = ask createTicket(..`a short ticket title for the request`<string>, 2);
234
+ const id = ask createTicket(`a short ticket title for the request`: string, 2);
232
235
  return id;
233
236
  }
234
237
  ```
@@ -239,14 +242,15 @@ spelling that carries a hint:
239
242
  ```tsi
240
243
  export infer function fileTicketCarefully(.request: string) {
241
244
  return ask createTicket`file the ticket exactly as the customer described it`(
242
- ..`a short ticket title`<string>,
243
- ..`priority 1-5, where 1 is most urgent`<number>,
245
+ `a short ticket title`: string,
246
+ `priority 1-5, where 1 is most urgent`: number,
244
247
  );
245
248
  }
246
249
  ```
247
250
 
248
- Every extractor slot needs an explicit `<T>`, and all slots of one call
249
- resolve together in a single provider round trip.
251
+ Every extractor slot needs an explicit type (an untyped template in the
252
+ arguments is a plain string), and all slots of one call resolve together in a
253
+ single provider round trip.
250
254
 
251
255
  `createTicket` is async, but `id` is a `string`, not a `Promise<string>` — a
252
256
  call intent awaits a promise-returning callee itself (`ask` ≈ `await`), so
@@ -254,9 +258,103 @@ call intent awaits a promise-returning callee itself (`ask` ≈ `await`), so
254
258
  ask, `.withRetry(n)` on a call intent re-invokes it on failure — only use it
255
259
  when the target is idempotent.
256
260
 
261
+ ## The tool-use project: offer functions, let the model call them
262
+
263
+ The same one-file shape for the third feature (`npm create nola` scaffolds
264
+ it as `tool-use`): the top-level `ask` offers two plain-TypeScript functions
265
+ as tools. The model calls them as it needs — here it lists a folder, reads
266
+ a file to be sure, then answers — and the typed answer is what `ask` yields.
267
+
268
+ ```ts
269
+ // src/tools.ts — plain TypeScript; the JSDoc is the contract the model sees
270
+ /**
271
+ * Lists the files under a directory on disc D.
272
+ * @param dir an absolute directory path, such as D:/docs
273
+ * @param pattern keeps only the paths that contain it
274
+ */
275
+ export async function searchFiles(dir: string, pattern?: string): Promise<string[]> { /* … */ return []; }
276
+
277
+ /** Reads a file's text. */
278
+ export async function readFile(path: string): Promise<string> { /* … */ return ""; }
279
+ ```
280
+
281
+ ```tsi
282
+ // src/main.tsi — run with: nola run src/main.tsi
283
+ import { readFile, searchFiles } from "./tools.js";
284
+
285
+ const files = ask `the files on disc D that belong to a US visa application` [searchFiles, readFile]: string[];
286
+
287
+ console.log(JSON.stringify(files));
288
+ ```
289
+
290
+ Parentheses run a function once with model-filled arguments (the
291
+ function-calling project); brackets offer functions the model may call zero
292
+ or more times. Cap the loop with `.withMaxTurns(n)` when a task should not
293
+ wander; return a failure as a value (`{ error: "no such file" }`) when the
294
+ model should recover from it, since a thrown error ends the ask.
295
+
296
+ ## Data in place: a bare value in the instruction
297
+
298
+ When the model needs a value beside the words that explain it, write the
299
+ value bare between texts (an ask sequence): it reaches the model as a
300
+ named input block where you wrote it. A `${}` hole splices the value into
301
+ your words instead — pick by what the model should treat it as.
302
+
303
+ ```tsi
304
+ // src/main.tsi
305
+ interface Person {
306
+ name: string;
307
+ }
308
+ declare const project: { id: string; lead: string };
309
+ declare const policy: string;
310
+
311
+ // data where it stands: <input name="project"> … </input>, then your next words
312
+ export const owner = ask `the person responsible for` project `given this policy` policy: Person;
313
+
314
+ // words: the id is spliced into the sentence
315
+ export const lead = ask `the lead of project ${project.id}`: string;
316
+ ```
317
+
318
+ The same shape puts words on a call (`` ask `register the applicant` createUser(`user id`: string) ``)
319
+ and words between tool lists (`` ask `find` [searchFiles] `or, when stale,` [readDir] `done`: string[] ``).
320
+ The kind follows the parts, never the data: texts and data extract, a call
321
+ with a typed slot is function calling, lists are tool use.
322
+
323
+ ## An agent loop with a live context statement
324
+
325
+ A context statement is read at EACH ask that sees it, so one placed before a
326
+ loop shows the model the current state on every pass — no re-prompting code:
327
+
328
+ ```tsi
329
+ type Problem = { brief: string; solved: boolean };
330
+
331
+ export infer function solveAll(.query: string) {
332
+ `Solve every problem in the query, one at a time.`
333
+ const solved: Problem[] = [];
334
+ `Already solved:` solved.map((p) => p.brief);
335
+ while (solved.length < 5) {
336
+ const next = ask `the next unsolved problem, or a problem with solved: true when none is left`: Problem;
337
+ if (next.solved) break;
338
+ solved.push(next);
339
+ }
340
+ return solved;
341
+ }
342
+ ```
343
+
344
+ Every `ask` inside the loop sees `Already solved: ["…", "…"]` with the list as
345
+ it is at that moment. The value is your words (JSON), not an `<input>` block;
346
+ use `const .solved` instead if the model should treat it as data.
347
+
348
+ The statement is rendered once per ask, not once per pass: five passes do not
349
+ put five copies in front of the model, and the model never sees a history of
350
+ earlier renderings. A statement written INSIDE the loop body is scoped to
351
+ those braces like a `const` — only the asks inside them see it, and an ask
352
+ after the loop does not. What changes from pass to pass is the value a
353
+ statement reads, never the set of statements.
354
+
257
355
  ## Typing the answers
258
356
 
259
- Prefer a NAMED, exported `type` or `interface` for `<T>` over an inline object
357
+ Prefer a NAMED, exported `type` or `interface` as the extractor's type over an inline object
260
358
  literal: it documents the contract, it is reusable from plain TS, and JSDoc
261
359
  comments on its members become descriptions in the schema the LLM sees.
262
360
 
@@ -279,7 +377,7 @@ export interface Invoice {
279
377
  }
280
378
 
281
379
  export infer function extractInvoice(.document: string) {
282
- return ask `the invoice data from the document`<Invoice>;
380
+ return ask `the invoice data from the document`: Invoice;
283
381
  }
284
382
  ```
285
383
 
@@ -296,9 +394,9 @@ export enum Sentiment {
296
394
  }
297
395
 
298
396
  export infer function triage(.message: string) {
299
- const category = ask `the category of the customer message`<Category>;
300
- const sentiment = ask `the overall sentiment of the message`<Sentiment>;
301
- const urgent = ask `does the message need urgent attention`<"yes" | "no">;
397
+ const category = ask `the category of the customer message`: Category;
398
+ const sentiment = ask `the overall sentiment of the message`: Sentiment;
399
+ const urgent = ask `does the message need urgent attention`: "yes" | "no";
302
400
  return { category, sentiment, urgent: urgent === "yes" };
303
401
  }
304
402
  ```
@@ -323,7 +421,7 @@ export interface Person {
323
421
  import type { Person } from "./models.js";
324
422
 
325
423
  export infer function extractPerson(.text: string) {
326
- return ask `the person described in the text`<Person>;
424
+ return ask `the person described in the text`: Person;
327
425
  }
328
426
  ```
329
427
 
@@ -347,7 +445,7 @@ export type TreeNode = {
347
445
  };
348
446
 
349
447
  export infer function parseTree(.input: string) {
350
- return ask `the tree structure described in the input`<TreeNode>;
448
+ return ask `the tree structure described in the input`: TreeNode;
351
449
  }
352
450
  ```
353
451
 
@@ -357,7 +455,7 @@ export infer function parseTree(.input: string) {
357
455
  export type CalendarEvent = { title: string; at: Date };
358
456
 
359
457
  export infer function nextEvent(.calendar: string) {
360
- const event = ask `the next event on the calendar`<CalendarEvent>;
458
+ const event = ask `the next event on the calendar`: CalendarEvent;
361
459
  const when: Date = event.at; // a Date, not a string
362
460
  return when;
363
461
  }