create-nola-lang 0.1.13 → 0.1.14
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 +8 -3
- package/dist/{chunk-LUSVIOAN.js → chunk-CB26Z47Y.js} +53 -87
- package/dist/examples.d.ts +4 -2
- package/dist/examples.js +4 -2
- package/dist/flow.d.ts +8 -2
- package/dist/flow.js +15 -12
- package/dist/github.d.ts +1 -1
- package/dist/github.js +1 -1
- package/dist/index.js +1 -1
- package/dist/launch.d.ts +2 -2
- package/dist/launch.js +1 -1
- package/dist/main.js +1 -1
- package/dist/node-version.d.ts +3 -2
- package/dist/node-version.js +4 -3
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +1 -1
- package/dist/registry.d.ts +19 -10
- package/dist/registry.js +20 -5
- package/dist/scaffold.d.ts +19 -15
- package/dist/scaffold.js +63 -113
- package/package.json +1 -1
- package/skills/nola/SKILL.md +48 -16
- package/skills/nola/references/config.md +5 -5
- package/skills/nola/references/patterns.md +67 -31
- package/skills/nola/references/pitfalls.md +200 -52
- package/skills/nola/references/syntax.md +221 -122
- package/templates/empty/package.json +5 -5
- package/templates/empty/src/main.tsi +8 -0
- package/templates/empty/src/main.ts +0 -5
- package/templates/feature-extraction/README.md +0 -20
- package/templates/feature-extraction/nola.config.ts +0 -14
- package/templates/feature-extraction/nola.replay.jsonl +0 -2
- package/templates/feature-extraction/package.json +0 -22
- package/templates/feature-extraction/src/main.tsi +0 -22
- package/templates/feature-extraction/tsconfig.json +0 -12
- package/templates/function-calling/README.md +0 -21
- package/templates/function-calling/nola.config.ts +0 -14
- package/templates/function-calling/nola.replay.jsonl +0 -1
- package/templates/function-calling/package.json +0 -22
- package/templates/function-calling/src/main.tsi +0 -12
- package/templates/function-calling/src/tickets.ts +0 -17
- package/templates/function-calling/tsconfig.json +0 -12
- package/templates/typescript-interop/README.md +0 -15
- package/templates/typescript-interop/nola.config.ts +0 -14
- package/templates/typescript-interop/nola.replay.jsonl +0 -1
- package/templates/typescript-interop/package.json +0 -22
- package/templates/typescript-interop/src/main.ts +0 -8
- package/templates/typescript-interop/src/person.tsi +0 -11
- package/templates/typescript-interop/tsconfig.json +0 -12
package/dist/scaffold.d.ts
CHANGED
|
@@ -13,16 +13,14 @@ export interface ScaffoldOptions {
|
|
|
13
13
|
/**
|
|
14
14
|
* The inference provider chosen in the wizard; default "none" (the
|
|
15
15
|
* template's own config). Any live provider replaces `nola.config.ts` with
|
|
16
|
-
* that provider's config
|
|
17
|
-
* the matching README notes.
|
|
16
|
+
* that provider's config and drops the offline templates' replay ledger.
|
|
18
17
|
*/
|
|
19
18
|
provider?: ProviderId;
|
|
20
19
|
/**
|
|
21
20
|
* The editor chosen in the wizard; default "none". Decides the next-steps
|
|
22
|
-
* comment
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* editor-neutral.
|
|
21
|
+
* comment that opens the template's entry file: with VS Code it names F5,
|
|
22
|
+
* breakpoints and the recommended extension (all of which the .vscode
|
|
23
|
+
* files the flow writes make true); without one it stays editor-neutral.
|
|
26
24
|
*/
|
|
27
25
|
ide?: "vscode" | "none";
|
|
28
26
|
}
|
|
@@ -42,23 +40,29 @@ export declare function vendorEnvVar(template: string, provider: ProviderId): st
|
|
|
42
40
|
*/
|
|
43
41
|
export declare function envExample(envVar: string): string;
|
|
44
42
|
/**
|
|
45
|
-
* Add the recommended `.gitignore` to
|
|
46
|
-
*
|
|
47
|
-
* so they carry none
|
|
43
|
+
* Add the recommended `.gitignore` to a template's files. Templates are
|
|
44
|
+
* copied as committed — examples from `examples/`, where the monorepo's root
|
|
45
|
+
* ignore file covers them — so they carry none; one a template does carry wins.
|
|
48
46
|
*/
|
|
49
47
|
export declare function withRecommendedGitignore(files: Map<string, string>): Promise<Map<string, string>>;
|
|
50
48
|
/**
|
|
51
|
-
* The comment that opens the entry file (
|
|
52
|
-
* of
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* what make F5 and the extension
|
|
49
|
+
* The comment that opens the entry file (the `.tsi` entry of a one-file
|
|
50
|
+
* template and of `empty`, else the plain-TS src/main.ts) — the file the
|
|
51
|
+
* scaffold lands the user on (VS Code opens it as the active editor), so it
|
|
52
|
+
* carries the first three things to do. The VS Code variant only ships with
|
|
53
|
+
* the editor step's .vscode files, which are what make F5 and the extension
|
|
54
|
+
* prompt real.
|
|
56
55
|
*/
|
|
57
56
|
export declare function nextStepsComment(ide: "vscode" | "none", template: string): string;
|
|
58
57
|
export declare function ownVersion(): Promise<string>;
|
|
59
58
|
/**
|
|
60
59
|
* Lay a template down into `targetDir`. One implementation serves both entry
|
|
61
|
-
* points
|
|
60
|
+
* points — the `create-nola-lang` bin (`npm create nola-lang`) and `nola init`
|
|
61
|
+
* — and both sources: the files are collected as committed, then the scaffold
|
|
62
|
+
* adds what a project needs on top — the chosen provider's config (dropping
|
|
63
|
+
* the replay ledger), the project name and the lockstep ranges in
|
|
64
|
+
* package.json, the recommended .gitignore, the vendor key's .env.example,
|
|
65
|
+
* and the next-steps comment at the top of the entry file.
|
|
62
66
|
*/
|
|
63
67
|
export declare function scaffold(targetDir: string, opts?: ScaffoldOptions): Promise<ScaffoldResult>;
|
|
64
68
|
//# sourceMappingURL=scaffold.d.ts.map
|
package/dist/scaffold.js
CHANGED
|
@@ -5,7 +5,8 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { collectExampleFromDisk, devExamplesDir, rewriteExamplePackageJson } from "./examples.js";
|
|
6
6
|
import { fetchExampleFromGitHub } from "./github.js";
|
|
7
7
|
import { providerById } from "./providers.js";
|
|
8
|
-
import { templateByName, templateNames } from "./registry.js";
|
|
8
|
+
import { entryFile, templateByName, templateNames } from "./registry.js";
|
|
9
|
+
/** This package's own templates: `empty` (the one builtin), `_providers/` and `_gitignore`. */
|
|
9
10
|
const TEMPLATES_DIR = fileURLToPath(new URL("../templates/", import.meta.url));
|
|
10
11
|
/** The config a live provider choice writes over the template's own (`templates/_providers/<id>.config.ts`). */
|
|
11
12
|
export function providerConfigUrl(provider) {
|
|
@@ -28,36 +29,36 @@ export function vendorEnvVar(template, provider) {
|
|
|
28
29
|
export function envExample(envVar) {
|
|
29
30
|
return `# Copy to .env and fill in — \`nola run\` applies .env before evaluating nola.config.ts.\n${envVar}=\n`;
|
|
30
31
|
}
|
|
31
|
-
/** _gitignore ships underscored
|
|
32
|
-
const RENAMES = { _gitignore: ".gitignore" };
|
|
33
|
-
/** The ONE recommended .gitignore — every scaffold gets it; no template keeps a copy of its own. */
|
|
32
|
+
/** The ONE recommended .gitignore — every scaffold gets it; no template keeps a copy of its own (_gitignore ships underscored: npm pack strips nested .gitignore files). */
|
|
34
33
|
const GITIGNORE_URL = new URL("../templates/_gitignore", import.meta.url);
|
|
35
34
|
/**
|
|
36
|
-
* Add the recommended `.gitignore` to
|
|
37
|
-
*
|
|
38
|
-
* so they carry none
|
|
35
|
+
* Add the recommended `.gitignore` to a template's files. Templates are
|
|
36
|
+
* copied as committed — examples from `examples/`, where the monorepo's root
|
|
37
|
+
* ignore file covers them — so they carry none; one a template does carry wins.
|
|
39
38
|
*/
|
|
40
39
|
export async function withRecommendedGitignore(files) {
|
|
41
40
|
if (!files.has(".gitignore"))
|
|
42
41
|
files.set(".gitignore", await readFile(GITIGNORE_URL, "utf8"));
|
|
43
42
|
return files;
|
|
44
43
|
}
|
|
45
|
-
/** Files whose __NAME__/__VERSION__, README-note and __NEXT_STEPS__ placeholders are substituted. */
|
|
46
|
-
const SUBSTITUTED = new Set(["package.json", "README.md", "main.ts", "main.tsi"]);
|
|
47
44
|
/**
|
|
48
|
-
* The comment that opens the entry file (
|
|
49
|
-
* of
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* what make F5 and the extension
|
|
45
|
+
* The comment that opens the entry file (the `.tsi` entry of a one-file
|
|
46
|
+
* template and of `empty`, else the plain-TS src/main.ts) — the file the
|
|
47
|
+
* scaffold lands the user on (VS Code opens it as the active editor), so it
|
|
48
|
+
* carries the first three things to do. The VS Code variant only ships with
|
|
49
|
+
* the editor step's .vscode files, which are what make F5 and the extension
|
|
50
|
+
* prompt real.
|
|
53
51
|
*/
|
|
54
52
|
export function nextStepsComment(ide, template) {
|
|
55
53
|
if (ide === "vscode") {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
// `empty`'s stub has no ask yet — the breakpoint goes on the one the user writes.
|
|
55
|
+
const breakpointIn = template === "empty"
|
|
56
|
+
? "on your first `ask` in this file"
|
|
57
|
+
: templateByName(template)?.entry
|
|
58
|
+
? "on the `ask` line below"
|
|
59
|
+
: template === "typescript-interop"
|
|
60
|
+
? "in src/person.tsi"
|
|
61
|
+
: "in your .tsi file";
|
|
61
62
|
return [
|
|
62
63
|
"// Next steps in VS Code:",
|
|
63
64
|
"// 1. Press F5 to run this file (.vscode/launch.json is already set up).",
|
|
@@ -73,40 +74,7 @@ export function nextStepsComment(ide, template) {
|
|
|
73
74
|
].join("\n");
|
|
74
75
|
}
|
|
75
76
|
/** Template files that only make sense for the offline (replay) configuration. */
|
|
76
|
-
const OFFLINE_ONLY =
|
|
77
|
-
const README_NOTES = {
|
|
78
|
-
offline: {
|
|
79
|
-
START_NOTE: "works offline, no API key needed",
|
|
80
|
-
PROVIDER_NOTE: "This project runs offline: `nola.config.ts` replays answers from the committed\n" +
|
|
81
|
-
"`nola.replay.jsonl` ledger. The ledger is keyed by the exact prompt, so once\n" +
|
|
82
|
-
"you edit the `.tsi` file or add your own asks, switch the config to a real\n" +
|
|
83
|
-
"model (see the comment in `nola.config.ts`): `model: \"nola\"` with a key from\n" +
|
|
84
|
-
"`npx nola-lang key` (25 free runs), or your own provider and its key.",
|
|
85
|
-
},
|
|
86
|
-
nola: {
|
|
87
|
-
START_NOTE: "uses your 25 free Nola runs (key in .env)",
|
|
88
|
-
PROVIDER_NOTE: "`nola.config.ts` sets `model: \"nola\"` — platform-served inference with the trial key the\n" +
|
|
89
|
-
"scaffold wrote to `.env` (git-ignored). Out of runs? `npx nola-lang account` opens\n" +
|
|
90
|
-
"your Nola account, where prepaid balance is added, or bring your\n" +
|
|
91
|
-
"own model (see the comment in `nola.config.ts`).",
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
/** The README notes for a chosen provider: offline, the Nola trial, or a vendor and the env var its factory reads. */
|
|
95
|
-
function readmeNotes(provider) {
|
|
96
|
-
if (provider === "none")
|
|
97
|
-
return README_NOTES.offline;
|
|
98
|
-
if (provider === "nola")
|
|
99
|
-
return README_NOTES.nola;
|
|
100
|
-
const def = providerById(provider);
|
|
101
|
-
if (!def?.envVar || !def.model)
|
|
102
|
-
throw new Error(`provider "${provider}" has no vendor config`);
|
|
103
|
-
return {
|
|
104
|
-
START_NOTE: `set ${def.envVar} in .env first`,
|
|
105
|
-
PROVIDER_NOTE: `\`nola.config.ts\` sets \`model: ${def.model}\` — ${def.label} serves inference with the key it reads\n` +
|
|
106
|
-
`from \`${def.envVar}\`. Copy \`.env.example\` to \`.env\` (git-ignored; \`nola run\` applies it) and fill in\n` +
|
|
107
|
-
`\`${def.envVar}\` before the first \`npm start\`, or switch models in \`nola.config.ts\`.`,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
77
|
+
const OFFLINE_ONLY = ["nola.replay.jsonl"];
|
|
110
78
|
export async function ownVersion() {
|
|
111
79
|
const pkg = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
112
80
|
return pkg.version;
|
|
@@ -123,9 +91,26 @@ async function prepareTarget(absRoot, force) {
|
|
|
123
91
|
for (const entry of entries)
|
|
124
92
|
await rm(join(absRoot, entry), { recursive: true, force: true });
|
|
125
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* A template's files as committed: `empty` from this package's templates/
|
|
96
|
+
* dir, every other template from examples/ — the dev checkout's on disk,
|
|
97
|
+
* else GitHub at the lockstep tag. Everything is buffered before the caller
|
|
98
|
+
* touches the target, so a failed fetch scaffolds nothing.
|
|
99
|
+
*/
|
|
100
|
+
async function collectTemplate(def, version) {
|
|
101
|
+
if (def.source === "builtin")
|
|
102
|
+
return collectExampleFromDisk(TEMPLATES_DIR, def.name);
|
|
103
|
+
const dev = await devExamplesDir();
|
|
104
|
+
return dev ? collectExampleFromDisk(dev, def.name) : fetchExampleFromGitHub(def.name, version);
|
|
105
|
+
}
|
|
126
106
|
/**
|
|
127
107
|
* Lay a template down into `targetDir`. One implementation serves both entry
|
|
128
|
-
* points
|
|
108
|
+
* points — the `create-nola-lang` bin (`npm create nola-lang`) and `nola init`
|
|
109
|
+
* — and both sources: the files are collected as committed, then the scaffold
|
|
110
|
+
* adds what a project needs on top — the chosen provider's config (dropping
|
|
111
|
+
* the replay ledger), the project name and the lockstep ranges in
|
|
112
|
+
* package.json, the recommended .gitignore, the vendor key's .env.example,
|
|
113
|
+
* and the next-steps comment at the top of the entry file.
|
|
129
114
|
*/
|
|
130
115
|
export async function scaffold(targetDir, opts = {}) {
|
|
131
116
|
const root = targetDir;
|
|
@@ -134,69 +119,34 @@ export async function scaffold(targetDir, opts = {}) {
|
|
|
134
119
|
const def = templateByName(template);
|
|
135
120
|
if (!def)
|
|
136
121
|
throw new Error(`unknown template "${template}" (valid: ${templateNames().join(", ")})`);
|
|
122
|
+
const version = await ownVersion();
|
|
123
|
+
const files = await collectTemplate(def, version);
|
|
137
124
|
await prepareTarget(absRoot, opts.force ?? false);
|
|
138
125
|
const name = opts.name ?? basename(absRoot);
|
|
139
|
-
const version = await ownVersion();
|
|
140
|
-
const envVar = vendorEnvVar(template, opts.provider ?? "none");
|
|
141
|
-
if (def.source === "example") {
|
|
142
|
-
const dev = await devExamplesDir();
|
|
143
|
-
const exampleFiles = await withRecommendedGitignore(dev ? await collectExampleFromDisk(dev, template) : await fetchExampleFromGitHub(template, version));
|
|
144
|
-
const manifest = exampleFiles.get("package.json");
|
|
145
|
-
if (manifest)
|
|
146
|
-
exampleFiles.set("package.json", rewriteExamplePackageJson(manifest, { name, version }));
|
|
147
|
-
if (envVar !== undefined && !exampleFiles.has(".env.example"))
|
|
148
|
-
exampleFiles.set(".env.example", envExample(envVar));
|
|
149
|
-
for (const [relPath, content] of exampleFiles) {
|
|
150
|
-
const target = join(absRoot, relPath);
|
|
151
|
-
await mkdir(join(target, ".."), { recursive: true });
|
|
152
|
-
await writeFile(target, content);
|
|
153
|
-
}
|
|
154
|
-
return { root, files: [...exampleFiles.keys()].sort() };
|
|
155
|
-
}
|
|
156
|
-
const templateRoot = join(TEMPLATES_DIR, template);
|
|
157
|
-
const versionRange = `^${version}`;
|
|
158
126
|
const provider = opts.provider ?? "none";
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
await
|
|
164
|
-
for (const entry of await readdir(join(templateRoot, fromDir), { withFileTypes: true })) {
|
|
165
|
-
const fromRel = join(fromDir, entry.name);
|
|
166
|
-
if (entry.isDirectory()) {
|
|
167
|
-
await copyDir(fromRel, join(relDir, entry.name));
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const toName = RENAMES[entry.name] ?? entry.name;
|
|
171
|
-
if (provider !== "none" && OFFLINE_ONLY.has(entry.name))
|
|
172
|
-
continue;
|
|
173
|
-
const toRel = join(relDir, toName);
|
|
174
|
-
// A live provider's config replaces the template's own (both builtin templates keep it at the root).
|
|
175
|
-
const source = provider !== "none" && relDir === "." && entry.name === "nola.config.ts"
|
|
176
|
-
? providerConfigUrl(provider)
|
|
177
|
-
: join(templateRoot, fromRel);
|
|
178
|
-
let content = await readFile(source, "utf8");
|
|
179
|
-
if (SUBSTITUTED.has(entry.name)) {
|
|
180
|
-
content = content
|
|
181
|
-
.replaceAll("__NAME__", name)
|
|
182
|
-
.replaceAll("__VERSION__", versionRange)
|
|
183
|
-
.replaceAll("__START_NOTE__", notes.START_NOTE)
|
|
184
|
-
.replaceAll("__PROVIDER_NOTE__", notes.PROVIDER_NOTE)
|
|
185
|
-
.replaceAll("__NEXT_STEPS__", nextSteps);
|
|
186
|
-
}
|
|
187
|
-
await writeFile(join(absRoot, toRel), content);
|
|
188
|
-
files.push(toRel.replaceAll("\\", "/"));
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
await copyDir(".", ".");
|
|
192
|
-
if (!files.includes(".gitignore")) {
|
|
193
|
-
await writeFile(join(absRoot, ".gitignore"), await readFile(GITIGNORE_URL, "utf8"));
|
|
194
|
-
files.push(".gitignore");
|
|
127
|
+
// A live provider's config replaces the template's own; the ledger it replayed goes with it.
|
|
128
|
+
if (provider !== "none") {
|
|
129
|
+
for (const offline of OFFLINE_ONLY)
|
|
130
|
+
files.delete(offline);
|
|
131
|
+
files.set("nola.config.ts", await readFile(providerConfigUrl(provider), "utf8"));
|
|
195
132
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
files.
|
|
133
|
+
const manifest = files.get("package.json");
|
|
134
|
+
if (manifest)
|
|
135
|
+
files.set("package.json", rewriteExamplePackageJson(manifest, { name, version }));
|
|
136
|
+
await withRecommendedGitignore(files);
|
|
137
|
+
const envVar = vendorEnvVar(template, provider);
|
|
138
|
+
if (envVar !== undefined && !files.has(".env.example"))
|
|
139
|
+
files.set(".env.example", envExample(envVar));
|
|
140
|
+
// The entry file greets the user with the next steps — it is what VS Code opens.
|
|
141
|
+
const entry = entryFile(template);
|
|
142
|
+
const program = files.get(entry);
|
|
143
|
+
if (program !== undefined)
|
|
144
|
+
files.set(entry, `${nextStepsComment(opts.ide ?? "none", template)}\n\n${program}`);
|
|
145
|
+
for (const [relPath, content] of files) {
|
|
146
|
+
const target = join(absRoot, relPath);
|
|
147
|
+
await mkdir(join(target, ".."), { recursive: true });
|
|
148
|
+
await writeFile(target, content);
|
|
199
149
|
}
|
|
200
|
-
return { root, files: files.sort() };
|
|
150
|
+
return { root, files: [...files.keys()].sort() };
|
|
201
151
|
}
|
|
202
152
|
//# sourceMappingURL=scaffold.js.map
|
package/package.json
CHANGED
package/skills/nola/SKILL.md
CHANGED
|
@@ -22,24 +22,39 @@ import type { Person } from "./types.js";
|
|
|
22
22
|
// thenable Intent<T>. `await`ing it (or `ask`) runs the inference.
|
|
23
23
|
infer function extractPerson(.text: string) {
|
|
24
24
|
// `ask` resolves an intent the way `await` resolves a promise.
|
|
25
|
-
const person = ask `Extract the person described in the text
|
|
25
|
+
const person = ask `Extract the person described in the text`: Person;
|
|
26
26
|
return person;
|
|
27
27
|
}
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
- `infer function name(...)` — declares a nola function.
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
- `infer function name(...)` — declares a nola function. Any bare template
|
|
31
|
+
literal on its own line in the body is a CONTEXT STATEMENT: text every
|
|
32
|
+
later `ask` in the invocation sees, read at each ask. It may continue with
|
|
33
|
+
values: `` `Page` oncall `when the ticket is an outage.` `` (a name, a call
|
|
34
|
+
or a bracketed literal; anything else in parentheses — but `await`, `ask`
|
|
35
|
+
and `this` cannot be values: assign them to a local first). Several are
|
|
36
|
+
legal, anywhere in the body; one before a loop shows the current state on
|
|
37
|
+
every pass. Visibility is lexical, like a `const`: a statement inside a
|
|
38
|
+
block reaches only the asks inside those braces, and statements never
|
|
39
|
+
accumulate across loop passes — each ask sees every statement visible at
|
|
40
|
+
its position exactly once, rendered at that ask. `await` is legal in the
|
|
41
|
+
body.
|
|
33
42
|
- `.name: T` parameters are CONTEXT parameters: their values are shown to
|
|
34
43
|
the LLM. Plain (no dot) parameters are ordinary values the LLM never
|
|
35
44
|
sees. `.` is only legal on infer-function parameters. Rule of thumb:
|
|
36
45
|
one dot marks a value flowing INTO the model (`.name`); a template after
|
|
37
46
|
`ask` is a value coming OUT.
|
|
38
|
-
- `` ask `prompt
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
- `` ask `prompt`: T `` — an extractor: asks the LLM for a `T`. The colon
|
|
48
|
+
is glued to the closing backtick; `` ask `prompt`<T> `` is the same
|
|
49
|
+
construct (write `: T` unless a page shows otherwise).
|
|
50
|
+
`${...}` interpolation works inside the backticks. The `..` is implied
|
|
51
|
+
directly after `ask` and in a call's argument list — a typed template
|
|
52
|
+
that starts an argument, or a value nested in a plain object/array
|
|
53
|
+
literal there, is an extractor: `` createTicket(`a short title`: string, 2) ``.
|
|
54
|
+
Anywhere else — a stored intent, a ternary, a spread, `new` — spell it
|
|
55
|
+
`` ..`prompt`: T `` (a typed `` `x`<T> `` elsewhere is NOLA2014, a
|
|
56
|
+
`` `x`: T `` a syntax error); `` ask ..`prompt` `` and `` fn(..`x`: T) ``
|
|
57
|
+
are still legal.
|
|
43
58
|
- `` ask fn`hint`(...) `` — or a plain call whose arguments contain an
|
|
44
59
|
extractor — is a call intent (the LLM fills the extractor-shaped
|
|
45
60
|
arguments, then the function runs). Only `` fn`hint`(...) `` carries
|
|
@@ -48,12 +63,29 @@ infer function extractPerson(.text: string) {
|
|
|
48
63
|
from `nola.config.ts`. The name must be a static identifier. When the
|
|
49
64
|
platform serves inference (`model: "nola"`) any unconfigured
|
|
50
65
|
name is legal — it is sent to the platform as a free-form inference profile.
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
the
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
- Values in an instruction — two spellings, one rule: `.name` (a contextual
|
|
67
|
+
parameter or `const .name` binding) is DATA, rendered as an `<input
|
|
68
|
+
name>` block the system turn marks as data; a value after text in a
|
|
69
|
+
context statement (`` `Page` oncall ``) or a `${expr}` hole in ANY
|
|
70
|
+
instruction backticks is part of the developer's words, spliced where it
|
|
71
|
+
stands (a string verbatim, a function its name, a call intent its callee
|
|
72
|
+
and arguments, anything else JSON) — the model FOLLOWS it, so interpolate
|
|
73
|
+
only text the developer controls (a team name, a count, a date) and put
|
|
74
|
+
anything a user or a document supplied in `.name`. A context statement is
|
|
75
|
+
read at EACH ask that sees it (module or body alike); an extractor's or
|
|
76
|
+
hint's text when the ask runs. A Nola construct (`ask`, `..`) inside a
|
|
77
|
+
context statement's value or hole, or in a call hint's hole, is NOLA2010
|
|
78
|
+
(written bare right after the text, NOLA1020); a call intent is a legal
|
|
79
|
+
value (a HINTED `` fn`hint`(…) `` one only in parentheses or a `${}` hole —
|
|
80
|
+
bare it is no call intent, and its extractor argument is NOLA2010). There
|
|
81
|
+
is no `${.member}` prompt scope (a dot cannot start a hole's expression).
|
|
82
|
+
See `references/syntax.md` → "Context statements" and "Values in an
|
|
83
|
+
instruction".
|
|
84
|
+
- The prompt itself is the developer's words in four tags — `<context
|
|
85
|
+
function|module>` with `<input name>` blocks, then `<task>` (`<task
|
|
86
|
+
call="fn">` for a call intent); the schema rides structured output, never
|
|
87
|
+
the prompt; `system.message` REPLACES the default system text. See
|
|
88
|
+
`references/syntax.md` → "The prompt".
|
|
57
89
|
|
|
58
90
|
## Where Nola diverges from TypeScript — hard rules
|
|
59
91
|
|
|
@@ -72,7 +104,7 @@ infer function extractPerson(.text: string) {
|
|
|
72
104
|
with an exported type's name (NOLA2011). `./x.tsi` with no `x.tsi` on disk
|
|
73
105
|
is the VIEW of `x.ts`: the same module plus those values — import plain-TS
|
|
74
106
|
types that way when you need their schema. Keep one basename per module.
|
|
75
|
-
- An extractor's
|
|
107
|
+
- An extractor's type should be a named, JSON-shaped type. The schema comes
|
|
76
108
|
from the RESOLVED type, so unions (a `kind` key makes them discriminated),
|
|
77
109
|
`Partial<T>`, `extends`, `Record<string, T>` and types from packages all
|
|
78
110
|
work; `Date` is revived to a real `Date`. `Map`/`Set`/`Promise`, functions
|
|
@@ -124,8 +124,8 @@ export default defineConfig({
|
|
|
124
124
|
|
|
125
125
|
```tsi
|
|
126
126
|
export infer function summarize(.text: string) {
|
|
127
|
-
const draft = ask with fast `a rough summary
|
|
128
|
-
return ask with careful `a polished summary of: ${draft}
|
|
127
|
+
const draft = ask with fast `a rough summary`: string;
|
|
128
|
+
return ask with careful `a polished summary of: ${draft}`: string;
|
|
129
129
|
}
|
|
130
130
|
```
|
|
131
131
|
|
|
@@ -232,11 +232,11 @@ the providers package:
|
|
|
232
232
|
"check": "nola check"
|
|
233
233
|
},
|
|
234
234
|
"dependencies": {
|
|
235
|
-
"@nola-lang/providers": "^0.1.
|
|
236
|
-
"@nola-lang/runtime": "^0.1.
|
|
235
|
+
"@nola-lang/providers": "^0.1.14",
|
|
236
|
+
"@nola-lang/runtime": "^0.1.14"
|
|
237
237
|
},
|
|
238
238
|
"devDependencies": {
|
|
239
|
-
"nola-lang": "^0.1.
|
|
239
|
+
"nola-lang": "^0.1.14",
|
|
240
240
|
"typescript": "^5.6.0"
|
|
241
241
|
},
|
|
242
242
|
"engines": { "node": ">=22.18" }
|