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.
- package/README.md +8 -3
- package/dist/add.d.ts +3 -0
- package/dist/add.js +2 -1
- package/dist/checkout.js +1 -0
- package/dist/{chunk-LUSVIOAN.js → chunk-UTVGG44K.js} +124 -95
- package/dist/examples.d.ts +4 -2
- package/dist/examples.js +4 -2
- package/dist/flow.d.ts +20 -3
- package/dist/flow.js +49 -17
- 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/package-manager.d.ts +5 -0
- package/dist/package-manager.js +7 -0
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +1 -1
- package/dist/registry.d.ts +21 -12
- package/dist/registry.js +27 -5
- package/dist/scaffold.d.ts +36 -16
- package/dist/scaffold.js +96 -116
- package/package.json +1 -1
- package/skills/nola/SKILL.md +72 -17
- package/skills/nola/references/config.md +35 -6
- package/skills/nola/references/patterns.md +129 -31
- package/skills/nola/references/pitfalls.md +318 -56
- package/skills/nola/references/syntax.md +345 -121
- 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.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,39 +29,61 @@ 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
|
-
/**
|
|
32
|
-
const
|
|
33
|
-
/** The
|
|
32
|
+
/** The Bun project's bunfig.toml: the preload for `bun src/main.tsi` and for `bun test`. */
|
|
33
|
+
export const BUNFIG_TOML = 'preload = ["@nola-lang/bun/preload"]\n\n[test]\npreload = ["@nola-lang/bun/preload"]\n';
|
|
34
|
+
/** The Bun floor @nola-lang/bun needs (process.getBuiltinModule). */
|
|
35
|
+
export const BUN_ENGINE = ">=1.3.0";
|
|
36
|
+
/** A Bun project's manifest additions on top of the template's: the start script, the devDependency, engines.bun. */
|
|
37
|
+
export function applyBunRuntime(content, opts) {
|
|
38
|
+
const pkg = JSON.parse(content);
|
|
39
|
+
const scripts = pkg.scripts ?? {};
|
|
40
|
+
scripts.start = `bun ${opts.entry}`;
|
|
41
|
+
pkg.scripts = scripts;
|
|
42
|
+
const dev = pkg.devDependencies ?? {};
|
|
43
|
+
dev["@nola-lang/bun"] = `^${opts.version}`;
|
|
44
|
+
pkg.devDependencies = dev;
|
|
45
|
+
const engines = pkg.engines ?? {};
|
|
46
|
+
engines.bun = BUN_ENGINE;
|
|
47
|
+
pkg.engines = engines;
|
|
48
|
+
return `${JSON.stringify(pkg, null, 2)}\n`;
|
|
49
|
+
}
|
|
50
|
+
/** 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
51
|
const GITIGNORE_URL = new URL("../templates/_gitignore", import.meta.url);
|
|
35
52
|
/**
|
|
36
|
-
* Add the recommended `.gitignore` to
|
|
37
|
-
*
|
|
38
|
-
* so they carry none
|
|
53
|
+
* Add the recommended `.gitignore` to a template's files. Templates are
|
|
54
|
+
* copied as committed — examples from `examples/`, where the monorepo's root
|
|
55
|
+
* ignore file covers them — so they carry none; one a template does carry wins.
|
|
39
56
|
*/
|
|
40
57
|
export async function withRecommendedGitignore(files) {
|
|
41
58
|
if (!files.has(".gitignore"))
|
|
42
59
|
files.set(".gitignore", await readFile(GITIGNORE_URL, "utf8"));
|
|
43
60
|
return files;
|
|
44
61
|
}
|
|
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
62
|
/**
|
|
48
|
-
* The comment that opens the entry file (
|
|
49
|
-
* of
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* what make F5 and the extension
|
|
63
|
+
* The comment that opens the entry file (the `.tsi` entry of a one-file
|
|
64
|
+
* template and of `empty`, else the plain-TS src/main.ts) — the file the
|
|
65
|
+
* scaffold lands the user on (VS Code opens it as the active editor), so it
|
|
66
|
+
* carries the first three things to do. The VS Code variant only ships with
|
|
67
|
+
* the editor step's .vscode files, which are what make F5 and the extension
|
|
68
|
+
* prompt real.
|
|
53
69
|
*/
|
|
54
|
-
export function nextStepsComment(ide, template) {
|
|
70
|
+
export function nextStepsComment(ide, template, runtime = "node") {
|
|
55
71
|
if (ide === "vscode") {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
72
|
+
// `empty`'s stub has no ask yet — the breakpoint goes on the one the user writes.
|
|
73
|
+
const breakpointIn = template === "empty"
|
|
74
|
+
? "on your first `ask` in this file"
|
|
75
|
+
: templateByName(template)?.entry
|
|
76
|
+
? "on the `ask` line below"
|
|
77
|
+
: template === "typescript-interop"
|
|
78
|
+
? "in src/person.tsi"
|
|
79
|
+
: "in your .tsi file";
|
|
80
|
+
// A Bun project still debugs under Node's loader: the .vscode files are valid whenever Node is present.
|
|
81
|
+
const runLine = runtime === "bun"
|
|
82
|
+
? "// 1. Run this file: bun start (F5 runs it too, under Node's loader — .vscode/launch.json is already set up)."
|
|
83
|
+
: "// 1. Press F5 to run this file (.vscode/launch.json is already set up).";
|
|
61
84
|
return [
|
|
62
85
|
"// Next steps in VS Code:",
|
|
63
|
-
|
|
86
|
+
runLine,
|
|
64
87
|
`// 2. Set a breakpoint ${breakpointIn} and press F5 again to step through the ask.`,
|
|
65
88
|
'// 3. Install the recommended "Nola" extension when VS Code offers it — IntelliSense,',
|
|
66
89
|
"// go to definition and diagnostics inside .tsi files.",
|
|
@@ -68,45 +91,12 @@ export function nextStepsComment(ide, template) {
|
|
|
68
91
|
}
|
|
69
92
|
return [
|
|
70
93
|
"// Next steps:",
|
|
71
|
-
|
|
94
|
+
`// 1. Run this file: ${runtime === "bun" ? "bun start" : "npm start"}`,
|
|
72
95
|
"// 2. Set up your editor (VS Code extension, debugging): https://nola.sh/docs/start/editor-setup/",
|
|
73
96
|
].join("\n");
|
|
74
97
|
}
|
|
75
98
|
/** 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
|
-
}
|
|
99
|
+
const OFFLINE_ONLY = ["nola.replay.jsonl"];
|
|
110
100
|
export async function ownVersion() {
|
|
111
101
|
const pkg = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
112
102
|
return pkg.version;
|
|
@@ -123,9 +113,26 @@ async function prepareTarget(absRoot, force) {
|
|
|
123
113
|
for (const entry of entries)
|
|
124
114
|
await rm(join(absRoot, entry), { recursive: true, force: true });
|
|
125
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* A template's files as committed: `empty` from this package's templates/
|
|
118
|
+
* dir, every other template from examples/ — the dev checkout's on disk,
|
|
119
|
+
* else GitHub at the lockstep tag. Everything is buffered before the caller
|
|
120
|
+
* touches the target, so a failed fetch scaffolds nothing.
|
|
121
|
+
*/
|
|
122
|
+
async function collectTemplate(def, version) {
|
|
123
|
+
if (def.source === "builtin")
|
|
124
|
+
return collectExampleFromDisk(TEMPLATES_DIR, def.name);
|
|
125
|
+
const dev = await devExamplesDir();
|
|
126
|
+
return dev ? collectExampleFromDisk(dev, def.name) : fetchExampleFromGitHub(def.name, version);
|
|
127
|
+
}
|
|
126
128
|
/**
|
|
127
129
|
* Lay a template down into `targetDir`. One implementation serves both entry
|
|
128
|
-
* points
|
|
130
|
+
* points — the `create-nola-lang` bin (`npm create nola-lang`) and `nola init`
|
|
131
|
+
* — and both sources: the files are collected as committed, then the scaffold
|
|
132
|
+
* adds what a project needs on top — the chosen provider's config (dropping
|
|
133
|
+
* the replay ledger), the project name and the lockstep ranges in
|
|
134
|
+
* package.json, the recommended .gitignore, the vendor key's .env.example,
|
|
135
|
+
* and the next-steps comment at the top of the entry file.
|
|
129
136
|
*/
|
|
130
137
|
export async function scaffold(targetDir, opts = {}) {
|
|
131
138
|
const root = targetDir;
|
|
@@ -134,69 +141,42 @@ export async function scaffold(targetDir, opts = {}) {
|
|
|
134
141
|
const def = templateByName(template);
|
|
135
142
|
if (!def)
|
|
136
143
|
throw new Error(`unknown template "${template}" (valid: ${templateNames().join(", ")})`);
|
|
144
|
+
const version = await ownVersion();
|
|
145
|
+
const files = await collectTemplate(def, version);
|
|
137
146
|
await prepareTarget(absRoot, opts.force ?? false);
|
|
138
147
|
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
148
|
const provider = opts.provider ?? "none";
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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");
|
|
149
|
+
const runtime = opts.runtime ?? "node";
|
|
150
|
+
const entry = entryFile(template);
|
|
151
|
+
// A live provider's config replaces the template's own; the ledger it replayed goes with it.
|
|
152
|
+
if (provider !== "none") {
|
|
153
|
+
for (const offline of OFFLINE_ONLY)
|
|
154
|
+
files.delete(offline);
|
|
155
|
+
files.set("nola.config.ts", await readFile(providerConfigUrl(provider), "utf8"));
|
|
156
|
+
}
|
|
157
|
+
const manifest = files.get("package.json");
|
|
158
|
+
if (manifest)
|
|
159
|
+
files.set("package.json", rewriteExamplePackageJson(manifest, { name, version }));
|
|
160
|
+
// A Bun project (spec 2026-10-14-bun-support): the preload, a `bun <entry>` start script, the devDependency, engines.bun.
|
|
161
|
+
if (runtime === "bun") {
|
|
162
|
+
files.set("bunfig.toml", BUNFIG_TOML);
|
|
163
|
+
const current = files.get("package.json");
|
|
164
|
+
if (current !== undefined)
|
|
165
|
+
files.set("package.json", applyBunRuntime(current, { entry, version }));
|
|
195
166
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
167
|
+
await withRecommendedGitignore(files);
|
|
168
|
+
const envVar = vendorEnvVar(template, provider);
|
|
169
|
+
if (envVar !== undefined && !files.has(".env.example"))
|
|
170
|
+
files.set(".env.example", envExample(envVar));
|
|
171
|
+
// The entry file greets the user with the next steps — it is what VS Code opens.
|
|
172
|
+
const program = files.get(entry);
|
|
173
|
+
if (program !== undefined)
|
|
174
|
+
files.set(entry, `${nextStepsComment(opts.ide ?? "none", template, runtime)}\n\n${program}`);
|
|
175
|
+
for (const [relPath, content] of files) {
|
|
176
|
+
const target = join(absRoot, relPath);
|
|
177
|
+
await mkdir(join(target, ".."), { recursive: true });
|
|
178
|
+
await writeFile(target, content);
|
|
199
179
|
}
|
|
200
|
-
return { root, files: files.sort() };
|
|
180
|
+
return { root, files: [...files.keys()].sort() };
|
|
201
181
|
}
|
|
202
182
|
//# sourceMappingURL=scaffold.js.map
|
package/package.json
CHANGED
package/skills/nola/SKILL.md
CHANGED
|
@@ -22,38 +22,88 @@ 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 on the closing backtick's line (glued or spaced); `` ask `prompt`<T> ``
|
|
49
|
+
is the same 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
|
|
46
61
|
instruction text.
|
|
62
|
+
- `` ask `use` admin `and` cfg.defaults `go`: T `` — an ASK SEQUENCE: a
|
|
63
|
+
bare value between texts is named data the model sees where it stands
|
|
64
|
+
(`${x}` stays your words); `` ask `words` fn(`slot`: T) `` is a call
|
|
65
|
+
intent with words; several `[lists]` union with your words between them;
|
|
66
|
+
the type glues to the last part. The kind follows the parts: texts and
|
|
67
|
+
data → extract, one call → function calling, lists → tool use; a list
|
|
68
|
+
beside a call is reserved (NOLA2019).
|
|
69
|
+
- `` ask `task` [searchFiles, fs.readFile]: T `` — a TOOL-USE ask: the
|
|
70
|
+
functions in brackets between the template and the type are tools the
|
|
71
|
+
model may call, as often as it needs, before it answers; Nola runs each
|
|
72
|
+
call, hands the result back and asks again, the typed answer ends the
|
|
73
|
+
loop. Parentheses RUN a function once (a call intent), brackets OFFER
|
|
74
|
+
functions. Entries are names or member chains (a call written there is
|
|
75
|
+
reserved, NOLA1022); the contract derives from the signature and JSDoc;
|
|
76
|
+
`.withMaxTurns(n)` / config `ask.maxTurns` (10) cap the turns (NOLA3020);
|
|
77
|
+
a tool that throws ends the ask (NOLA3021); the model must support tool
|
|
78
|
+
calls — openai/anthropic/google/"nola", or `mockProvider(…, { tools:
|
|
79
|
+
true })` (NOLA3019 otherwise).
|
|
47
80
|
- `ask with <modelName> <intent>` — routes one ask through a named model
|
|
48
81
|
from `nola.config.ts`. The name must be a static identifier. When the
|
|
49
82
|
platform serves inference (`model: "nola"`) any unconfigured
|
|
50
83
|
name is legal — it is sent to the platform as a free-form inference profile.
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
the
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
84
|
+
- Values in an instruction — two spellings, one rule: `.name` (a contextual
|
|
85
|
+
parameter or `const .name` binding) is DATA, rendered as an `<input
|
|
86
|
+
name>` block the system turn marks as data; a value after text in a
|
|
87
|
+
context statement (`` `Page` oncall ``) or a `${expr}` hole in ANY
|
|
88
|
+
instruction backticks is part of the developer's words, spliced where it
|
|
89
|
+
stands (a string verbatim, a function its name, a call intent its callee
|
|
90
|
+
and arguments, anything else JSON) — the model FOLLOWS it, so interpolate
|
|
91
|
+
only text the developer controls (a team name, a count, a date) and put
|
|
92
|
+
anything a user or a document supplied in `.name`. A context statement is
|
|
93
|
+
read at EACH ask that sees it (module or body alike); an extractor's or
|
|
94
|
+
hint's text when the ask runs. A Nola construct (`ask`, `..`) inside a
|
|
95
|
+
context statement's value or hole, or in a call hint's hole, is NOLA2010
|
|
96
|
+
(written bare right after the text, NOLA1020); a call intent is a legal
|
|
97
|
+
value (a HINTED `` fn`hint`(…) `` one only in parentheses or a `${}` hole —
|
|
98
|
+
bare it is no call intent, and its extractor argument is NOLA2010). There
|
|
99
|
+
is no `${.member}` prompt scope (a dot cannot start a hole's expression).
|
|
100
|
+
See `references/syntax.md` → "Context statements" and "Values in an
|
|
101
|
+
instruction".
|
|
102
|
+
- The prompt itself is the developer's words in four tags — `<context
|
|
103
|
+
function|module>` with `<input name>` blocks, then `<task>` (`<task
|
|
104
|
+
call="fn">` for a call intent); the schema rides structured output, never
|
|
105
|
+
the prompt; `system.message` REPLACES the default system text. See
|
|
106
|
+
`references/syntax.md` → "The prompt".
|
|
57
107
|
|
|
58
108
|
## Where Nola diverges from TypeScript — hard rules
|
|
59
109
|
|
|
@@ -64,6 +114,11 @@ infer function extractPerson(.text: string) {
|
|
|
64
114
|
`import { f } from "./x.tsi"`. Imports of plain TS use NodeNext style:
|
|
65
115
|
`import { g } from "./y.js"` (the `.js` extension, even though the source
|
|
66
116
|
file is `.ts`).
|
|
117
|
+
- `.tsi` runs from source through the Node loader (`nola run`, Node ≥ 22.18)
|
|
118
|
+
or, on Bun, through `@nola-lang/bun`'s preload — `preload =
|
|
119
|
+
["@nola-lang/bun/preload"]` in `bunfig.toml`, then `bun src/main.tsi`;
|
|
120
|
+
`nola check` / `nola build` stay on Node (`references/config.md`, "Running
|
|
121
|
+
on Bun").
|
|
67
122
|
- Every `export type` / `export interface` in a `.tsi` is ALSO a runtime
|
|
68
123
|
value (`User.toJsonSchema()`, `User.validate(v)`, `User.parse(v)`,
|
|
69
124
|
`User["~standard"]` — Standard Schema AND Standard JSON Schema, so
|
|
@@ -72,7 +127,7 @@ infer function extractPerson(.text: string) {
|
|
|
72
127
|
with an exported type's name (NOLA2011). `./x.tsi` with no `x.tsi` on disk
|
|
73
128
|
is the VIEW of `x.ts`: the same module plus those values — import plain-TS
|
|
74
129
|
types that way when you need their schema. Keep one basename per module.
|
|
75
|
-
- An extractor's
|
|
130
|
+
- An extractor's type should be a named, JSON-shaped type. The schema comes
|
|
76
131
|
from the RESOLVED type, so unions (a `kind` key makes them discriminated),
|
|
77
132
|
`Partial<T>`, `extends`, `Record<string, T>` and types from packages all
|
|
78
133
|
work; `Date` is revived to a real `Date`. `Map`/`Set`/`Promise`, functions
|
|
@@ -92,5 +147,5 @@ Read these before non-trivial work:
|
|
|
92
147
|
|
|
93
148
|
- `references/syntax.md` — full grammar and semantics of every construct
|
|
94
149
|
- `references/patterns.md` — worked examples (extraction, call intents)
|
|
95
|
-
- `references/config.md` — `nola.config.ts`, providers, project layout
|
|
150
|
+
- `references/config.md` — `nola.config.ts`, providers, project layout, running on Bun
|
|
96
151
|
- `references/pitfalls.md` — common errors (NOLA codes) and their fixes
|
|
@@ -39,7 +39,9 @@ export default defineConfig({
|
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
`openai({ model })` requires an explicit model; optional fields are `apiKey`,
|
|
42
|
-
`apiKeyEnv` (default `"OPENAI_API_KEY"`), `baseUrl` and `fetch`.
|
|
42
|
+
`apiKeyEnv` (default `"OPENAI_API_KEY"`), `baseUrl` and `fetch`. With a custom
|
|
43
|
+
`fetch`, `apiKey`/`apiKeyEnv` are optional: the fetch authenticates, and without
|
|
44
|
+
a key no vendor auth header is sent (the same rule for every vendor factory).
|
|
43
45
|
|
|
44
46
|
`typesafe()` (typesafe.ai's Jev, reads `TYPESAFE_API_KEY`, model defaults to
|
|
45
47
|
`jev-latest`) is NOT a chat model: it is the DECISION model that answers the
|
|
@@ -124,8 +126,8 @@ export default defineConfig({
|
|
|
124
126
|
|
|
125
127
|
```tsi
|
|
126
128
|
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}
|
|
129
|
+
const draft = ask with fast `a rough summary`: string;
|
|
130
|
+
return ask with careful `a polished summary of: ${draft}`: string;
|
|
129
131
|
}
|
|
130
132
|
```
|
|
131
133
|
|
|
@@ -232,11 +234,11 @@ the providers package:
|
|
|
232
234
|
"check": "nola check"
|
|
233
235
|
},
|
|
234
236
|
"dependencies": {
|
|
235
|
-
"@nola-lang/providers": "^0.1.
|
|
236
|
-
"@nola-lang/runtime": "^0.1.
|
|
237
|
+
"@nola-lang/providers": "^0.1.15",
|
|
238
|
+
"@nola-lang/runtime": "^0.1.15"
|
|
237
239
|
},
|
|
238
240
|
"devDependencies": {
|
|
239
|
-
"nola-lang": "^0.1.
|
|
241
|
+
"nola-lang": "^0.1.15",
|
|
240
242
|
"typescript": "^5.6.0"
|
|
241
243
|
},
|
|
242
244
|
"engines": { "node": ">=22.18" }
|
|
@@ -270,6 +272,33 @@ for you). Do not mix versions.
|
|
|
270
272
|
a bare `npx nola` outside a project resolves an unrelated npm package named
|
|
271
273
|
`nola`.)
|
|
272
274
|
|
|
275
|
+
### Running on Bun
|
|
276
|
+
|
|
277
|
+
`bun create nola` scaffolds a Bun project: `@nola-lang/bun` in devDependencies,
|
|
278
|
+
`bunfig.toml` with its preload, `start: "bun src/main.tsi"`. Any Node project
|
|
279
|
+
becomes one by adding the same three things:
|
|
280
|
+
|
|
281
|
+
```toml
|
|
282
|
+
# bunfig.toml
|
|
283
|
+
preload = ["@nola-lang/bun/preload"]
|
|
284
|
+
|
|
285
|
+
[test]
|
|
286
|
+
preload = ["@nola-lang/bun/preload"]
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
- `bun src/main.tsi` / `bun --watch src/main.tsi` run `.tsi` from source;
|
|
290
|
+
`bun test` files import `.tsi` modules. Bun ≥ 1.3.
|
|
291
|
+
- One-step bundles and executables: a build script with
|
|
292
|
+
`Bun.build({ entrypoints: ["src/main.tsi"], plugins: [nola()], target: "bun",
|
|
293
|
+
compile: { outfile: "my-app" } })` (`import { nola } from "@nola-lang/bun"`).
|
|
294
|
+
- `nola build` output runs under `bun` with nothing added
|
|
295
|
+
(`bun dist/src/main.tsi.js`, `bun build --compile dist/src/main.tsi.js`).
|
|
296
|
+
- `nola check`, `nola build`, `nola console` run on Node (`bun run check`
|
|
297
|
+
launches them there). `nola run` is the Node loader — under Bun it is
|
|
298
|
+
NOLA3015; use the preload. No source map rides a Bun plugin: stack-trace
|
|
299
|
+
lines through `.tsi` are right at module level and in plain functions, ONE
|
|
300
|
+
TOO HIGH inside an `infer function` body, and columns inside an `ask` drift.
|
|
301
|
+
|
|
273
302
|
## tsconfig.json
|
|
274
303
|
|
|
275
304
|
```json
|