create-nola-lang 0.1.12 → 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/README.md
CHANGED
|
@@ -25,8 +25,13 @@ symlink to it for Claude Code) are written without a question; `--ide none` and
|
|
|
25
25
|
|
|
26
26
|
You get one template per feature — `feature-extraction` (the default) is one
|
|
27
27
|
`src/main.tsi` with a top-level `ask`, `function-calling` the same with a call
|
|
28
|
-
intent over an async function in `src/tickets.ts`, `
|
|
29
|
-
`src/person.tsi` plus a plain-TS
|
|
30
|
-
|
|
28
|
+
intent over an async function in `src/tickets.ts`, `agent-loop` a top-level
|
|
29
|
+
`while` around an ask, `typescript-interop` is `src/person.tsi` plus a plain-TS
|
|
30
|
+
consumer, `triage-ticket` the one-file shape on typesafe.ai — each with
|
|
31
|
+
`nola.config.ts` and a tsconfig, and the first two and `typescript-interop`
|
|
32
|
+
with a recorded replay ledger so the first run works without any API key.
|
|
33
|
+
Every template but `empty` is one of the curated examples in the Nola
|
|
34
|
+
repository, fetched at the release tag matching this package's version;
|
|
35
|
+
`empty` (config, tsconfig and a stub entry) ships inside the package. `npm create nola`
|
|
31
36
|
is a short alias of this package (`create-nola`); `nola init` (from the
|
|
32
37
|
`nola-lang` package) lays down the same templates.
|
|
@@ -16,7 +16,7 @@ var PROVIDERS = [
|
|
|
16
16
|
},
|
|
17
17
|
{ id: "google", label: "Gemini", hint: 'google("gemini-2.5-flash"), reads GEMINI_API_KEY', envVar: "GEMINI_API_KEY", model: 'google("gemini-2.5-flash")' },
|
|
18
18
|
{ id: "typesafe", label: "typesafe.ai", hint: "typesafe(), reads TYPESAFE_API_KEY", envVar: "TYPESAFE_API_KEY", model: "typesafe()" },
|
|
19
|
-
{ id: "none", label: "Skip for now", hint: "the
|
|
19
|
+
{ id: "none", label: "Skip for now", hint: "keeps the template's own config (the recorded ones run offline); pick a model in nola.config.ts later" }
|
|
20
20
|
];
|
|
21
21
|
function providerById(id2) {
|
|
22
22
|
return PROVIDERS.find((p) => p.id === id2);
|
|
@@ -170,17 +170,32 @@ async function fetchExampleFromGitHub(name, version, fetchImpl = fetch) {
|
|
|
170
170
|
// src/registry.ts
|
|
171
171
|
var DEFAULT_ENTRY = "src/main.ts";
|
|
172
172
|
var TEMPLATES = [
|
|
173
|
-
{
|
|
173
|
+
{
|
|
174
|
+
name: "feature-extraction",
|
|
175
|
+
label: "extract typed data from context",
|
|
176
|
+
source: "example",
|
|
177
|
+
featured: true,
|
|
178
|
+
entry: "src/main.tsi"
|
|
179
|
+
},
|
|
174
180
|
{
|
|
175
181
|
name: "function-calling",
|
|
176
182
|
label: "call an async function from a TypeScript file",
|
|
177
|
-
source: "
|
|
183
|
+
source: "example",
|
|
184
|
+
featured: true,
|
|
185
|
+
entry: "src/main.tsi"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "agent-loop",
|
|
189
|
+
label: "an agent loop in one .tsi file: a top-level while with an ask, and a context statement read on every pass",
|
|
190
|
+
source: "example",
|
|
191
|
+
featured: true,
|
|
178
192
|
entry: "src/main.tsi"
|
|
179
193
|
},
|
|
180
194
|
{
|
|
181
195
|
name: "typescript-interop",
|
|
182
196
|
label: "an infer function example, imported and awaited from plain TypeScript",
|
|
183
|
-
source: "
|
|
197
|
+
source: "example",
|
|
198
|
+
featured: true
|
|
184
199
|
},
|
|
185
200
|
{
|
|
186
201
|
name: "triage-ticket",
|
|
@@ -190,7 +205,7 @@ var TEMPLATES = [
|
|
|
190
205
|
entry: "src/main.tsi",
|
|
191
206
|
provider: { label: "typesafe.ai", envVar: "TYPESAFE_API_KEY" }
|
|
192
207
|
},
|
|
193
|
-
{ name: "empty", label: "nola.config + tsconfig only, bring your own code", source: "builtin" },
|
|
208
|
+
{ name: "empty", label: "nola.config + tsconfig only, bring your own code", source: "builtin", entry: "src/main.tsi" },
|
|
194
209
|
{ name: "file-ticket", label: "call intents: the model fills a function's arguments", source: "example" },
|
|
195
210
|
{ name: "extract-resume", label: "nested arrays of objects, JSDoc schema descriptions", source: "example" },
|
|
196
211
|
{ name: "extract-invoice", label: "same-file type references, optional fields", source: "example" },
|
|
@@ -231,16 +246,14 @@ function envExample(envVar) {
|
|
|
231
246
|
${envVar}=
|
|
232
247
|
`;
|
|
233
248
|
}
|
|
234
|
-
var RENAMES = { _gitignore: ".gitignore" };
|
|
235
249
|
var GITIGNORE_URL = new URL("../templates/_gitignore", import.meta.url);
|
|
236
250
|
async function withRecommendedGitignore(files) {
|
|
237
251
|
if (!files.has(".gitignore")) files.set(".gitignore", await readFile3(GITIGNORE_URL, "utf8"));
|
|
238
252
|
return files;
|
|
239
253
|
}
|
|
240
|
-
var SUBSTITUTED = /* @__PURE__ */ new Set(["package.json", "README.md", "main.ts", "main.tsi"]);
|
|
241
254
|
function nextStepsComment(ide, template) {
|
|
242
255
|
if (ide === "vscode") {
|
|
243
|
-
const breakpointIn = templateByName(template)?.entry ? "on the `ask` line below" : template === "typescript-interop" ? "in src/person.tsi" : "in your .tsi file";
|
|
256
|
+
const breakpointIn = template === "empty" ? "on your first `ask` in this file" : templateByName(template)?.entry ? "on the `ask` line below" : template === "typescript-interop" ? "in src/person.tsi" : "in your .tsi file";
|
|
244
257
|
return [
|
|
245
258
|
"// Next steps in VS Code:",
|
|
246
259
|
"// 1. Press F5 to run this file (.vscode/launch.json is already set up).",
|
|
@@ -255,29 +268,7 @@ function nextStepsComment(ide, template) {
|
|
|
255
268
|
"// 2. Set up your editor (VS Code extension, debugging): https://nola.sh/docs/start/editor-setup/"
|
|
256
269
|
].join("\n");
|
|
257
270
|
}
|
|
258
|
-
var OFFLINE_ONLY =
|
|
259
|
-
var README_NOTES = {
|
|
260
|
-
offline: {
|
|
261
|
-
START_NOTE: "works offline, no API key needed",
|
|
262
|
-
PROVIDER_NOTE: 'This project runs offline: `nola.config.ts` replays answers from the committed\n`nola.replay.jsonl` ledger. The ledger is keyed by the exact prompt, so once\nyou edit the `.tsi` file or add your own asks, switch the config to a real\nmodel (see the comment in `nola.config.ts`): `model: "nola"` with a key from\n`npx nola-lang key` (25 free runs), or your own provider and its key.'
|
|
263
|
-
},
|
|
264
|
-
nola: {
|
|
265
|
-
START_NOTE: "uses your 25 free Nola runs (key in .env)",
|
|
266
|
-
PROVIDER_NOTE: '`nola.config.ts` sets `model: "nola"` \u2014 platform-served inference with the trial key the\nscaffold wrote to `.env` (git-ignored). Out of runs? `npx nola-lang account` opens\nyour Nola account, where prepaid balance is added, or bring your\nown model (see the comment in `nola.config.ts`).'
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
function readmeNotes(provider) {
|
|
270
|
-
if (provider === "none") return README_NOTES.offline;
|
|
271
|
-
if (provider === "nola") return README_NOTES.nola;
|
|
272
|
-
const def = providerById(provider);
|
|
273
|
-
if (!def?.envVar || !def.model) throw new Error(`provider "${provider}" has no vendor config`);
|
|
274
|
-
return {
|
|
275
|
-
START_NOTE: `set ${def.envVar} in .env first`,
|
|
276
|
-
PROVIDER_NOTE: `\`nola.config.ts\` sets \`model: ${def.model}\` \u2014 ${def.label} serves inference with the key it reads
|
|
277
|
-
from \`${def.envVar}\`. Copy \`.env.example\` to \`.env\` (git-ignored; \`nola run\` applies it) and fill in
|
|
278
|
-
\`${def.envVar}\` before the first \`npm start\`, or switch models in \`nola.config.ts\`.`
|
|
279
|
-
};
|
|
280
|
-
}
|
|
271
|
+
var OFFLINE_ONLY = ["nola.replay.jsonl"];
|
|
281
272
|
async function ownVersion() {
|
|
282
273
|
const pkg = JSON.parse(await readFile3(new URL("../package.json", import.meta.url), "utf8"));
|
|
283
274
|
return pkg.version;
|
|
@@ -289,67 +280,42 @@ async function prepareTarget(absRoot, force) {
|
|
|
289
280
|
if (!force) throw new Error(`target directory ${absRoot} is not empty`);
|
|
290
281
|
for (const entry of entries) await rm2(join3(absRoot, entry), { recursive: true, force: true });
|
|
291
282
|
}
|
|
283
|
+
async function collectTemplate(def, version) {
|
|
284
|
+
if (def.source === "builtin") return collectExampleFromDisk(TEMPLATES_DIR, def.name);
|
|
285
|
+
const dev = await devExamplesDir();
|
|
286
|
+
return dev ? collectExampleFromDisk(dev, def.name) : fetchExampleFromGitHub(def.name, version);
|
|
287
|
+
}
|
|
292
288
|
async function scaffold(targetDir, opts = {}) {
|
|
293
289
|
const root = targetDir;
|
|
294
290
|
const absRoot = resolve(targetDir);
|
|
295
291
|
const template = opts.template ?? "feature-extraction";
|
|
296
292
|
const def = templateByName(template);
|
|
297
293
|
if (!def) throw new Error(`unknown template "${template}" (valid: ${templateNames().join(", ")})`);
|
|
294
|
+
const version = await ownVersion();
|
|
295
|
+
const files = await collectTemplate(def, version);
|
|
298
296
|
await prepareTarget(absRoot, opts.force ?? false);
|
|
299
297
|
const name = opts.name ?? basename(absRoot);
|
|
300
|
-
const version = await ownVersion();
|
|
301
|
-
const envVar = vendorEnvVar(template, opts.provider ?? "none");
|
|
302
|
-
if (def.source === "example") {
|
|
303
|
-
const dev = await devExamplesDir();
|
|
304
|
-
const exampleFiles = await withRecommendedGitignore(
|
|
305
|
-
dev ? await collectExampleFromDisk(dev, template) : await fetchExampleFromGitHub(template, version)
|
|
306
|
-
);
|
|
307
|
-
const manifest = exampleFiles.get("package.json");
|
|
308
|
-
if (manifest) exampleFiles.set("package.json", rewriteExamplePackageJson(manifest, { name, version }));
|
|
309
|
-
if (envVar !== void 0 && !exampleFiles.has(".env.example")) exampleFiles.set(".env.example", envExample(envVar));
|
|
310
|
-
for (const [relPath, content] of exampleFiles) {
|
|
311
|
-
const target = join3(absRoot, relPath);
|
|
312
|
-
await mkdir2(join3(target, ".."), { recursive: true });
|
|
313
|
-
await writeFile2(target, content);
|
|
314
|
-
}
|
|
315
|
-
return { root, files: [...exampleFiles.keys()].sort() };
|
|
316
|
-
}
|
|
317
|
-
const templateRoot = join3(TEMPLATES_DIR, template);
|
|
318
|
-
const versionRange = `^${version}`;
|
|
319
298
|
const provider = opts.provider ?? "none";
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
339
|
-
await writeFile2(join3(absRoot, toRel), content);
|
|
340
|
-
files.push(toRel.replaceAll("\\", "/"));
|
|
341
|
-
}
|
|
342
|
-
};
|
|
343
|
-
await copyDir(".", ".");
|
|
344
|
-
if (!files.includes(".gitignore")) {
|
|
345
|
-
await writeFile2(join3(absRoot, ".gitignore"), await readFile3(GITIGNORE_URL, "utf8"));
|
|
346
|
-
files.push(".gitignore");
|
|
347
|
-
}
|
|
348
|
-
if (envVar !== void 0) {
|
|
349
|
-
await writeFile2(join3(absRoot, ".env.example"), envExample(envVar));
|
|
350
|
-
files.push(".env.example");
|
|
299
|
+
if (provider !== "none") {
|
|
300
|
+
for (const offline of OFFLINE_ONLY) files.delete(offline);
|
|
301
|
+
files.set("nola.config.ts", await readFile3(providerConfigUrl(provider), "utf8"));
|
|
302
|
+
}
|
|
303
|
+
const manifest = files.get("package.json");
|
|
304
|
+
if (manifest) files.set("package.json", rewriteExamplePackageJson(manifest, { name, version }));
|
|
305
|
+
await withRecommendedGitignore(files);
|
|
306
|
+
const envVar = vendorEnvVar(template, provider);
|
|
307
|
+
if (envVar !== void 0 && !files.has(".env.example")) files.set(".env.example", envExample(envVar));
|
|
308
|
+
const entry = entryFile(template);
|
|
309
|
+
const program = files.get(entry);
|
|
310
|
+
if (program !== void 0) files.set(entry, `${nextStepsComment(opts.ide ?? "none", template)}
|
|
311
|
+
|
|
312
|
+
${program}`);
|
|
313
|
+
for (const [relPath, content] of files) {
|
|
314
|
+
const target = join3(absRoot, relPath);
|
|
315
|
+
await mkdir2(join3(target, ".."), { recursive: true });
|
|
316
|
+
await writeFile2(target, content);
|
|
351
317
|
}
|
|
352
|
-
return { root, files: files.sort() };
|
|
318
|
+
return { root, files: [...files.keys()].sort() };
|
|
353
319
|
}
|
|
354
320
|
|
|
355
321
|
// src/add.ts
|
|
@@ -1571,7 +1537,7 @@ function nodeVersionWarning(version = process.versions.node) {
|
|
|
1571
1537
|
if (nodeSupportsTypeStripping(version)) return void 0;
|
|
1572
1538
|
const lines = [
|
|
1573
1539
|
`Node ${version} is older than Nola needs: Node >= ${NODE_FLOOR}, the first release that runs .ts files natively.`,
|
|
1574
|
-
"On this Node, `npm start` and the VS Code F5 launch fail with ERR_UNKNOWN_FILE_EXTENSION
|
|
1540
|
+
"On this Node, `npm start` and the VS Code F5 launch fail with ERR_UNKNOWN_FILE_EXTENSION on the first plain .ts file they load."
|
|
1575
1541
|
];
|
|
1576
1542
|
if (hasStripTypesFlag(version)) {
|
|
1577
1543
|
lines.push(
|
|
@@ -1614,15 +1580,15 @@ var TEMPLATE_QUESTION = "Select a template:";
|
|
|
1614
1580
|
var EXAMPLE_QUESTION = "Select an example:";
|
|
1615
1581
|
var MORE_EXAMPLES = "more-examples";
|
|
1616
1582
|
var BACK = "back";
|
|
1617
|
-
var builtinNames = () => TEMPLATES.filter((t) => t.source === "builtin").map((t) => `"${t.name}"`).join(", ");
|
|
1618
1583
|
function templateMenu() {
|
|
1619
1584
|
const featured = featuredNames().map((name) => templateOption(templateByName(name)));
|
|
1620
1585
|
return [...featured, { value: MORE_EXAMPLES, label: "More examples\u2026", hint: exampleNames().join(", ") }];
|
|
1621
1586
|
}
|
|
1622
1587
|
function exampleMenu() {
|
|
1623
1588
|
const examples = exampleNames().map((name) => templateOption(templateByName(name)));
|
|
1624
|
-
return [...examples, { value: BACK, label: "\u2190 Back", hint: "the
|
|
1589
|
+
return [...examples, { value: BACK, label: "\u2190 Back", hint: "the first menu" }];
|
|
1625
1590
|
}
|
|
1591
|
+
var FETCH_FAILURE_HINT = 'Only "empty" ships inside the CLI and needs no download \u2014 every other template is fetched from GitHub.';
|
|
1626
1592
|
async function selectTemplate(prompter) {
|
|
1627
1593
|
for (; ; ) {
|
|
1628
1594
|
const choice = await prompter.select(TEMPLATE_QUESTION, templateMenu());
|
|
@@ -1832,7 +1798,7 @@ function plainPrompter() {
|
|
|
1832
1798
|
var FLOW_OPTIONS = {
|
|
1833
1799
|
template: {
|
|
1834
1800
|
type: "string",
|
|
1835
|
-
description: "template to scaffold
|
|
1801
|
+
description: "template to scaffold: feature-extraction (the default), function-calling, agent-loop, typescript-interop, triage-ticket, empty, or another curated example"
|
|
1836
1802
|
},
|
|
1837
1803
|
add: { type: "boolean", description: "add Nola to the existing project in [dir] instead of scaffolding" },
|
|
1838
1804
|
ide: { type: "string", description: "editor setup: vscode (the default) | none" },
|
|
@@ -2070,7 +2036,7 @@ Next steps:
|
|
|
2070
2036
|
} catch (err) {
|
|
2071
2037
|
if (err instanceof ExampleFetchError && interactive) {
|
|
2072
2038
|
prompter.note(`${err.message}
|
|
2073
|
-
|
|
2039
|
+
${FETCH_FAILURE_HINT}`);
|
|
2074
2040
|
input.dir = outcome.dir;
|
|
2075
2041
|
input.template = void 0;
|
|
2076
2042
|
continue;
|
package/dist/examples.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ export declare function devExamplesDir(): Promise<string | null>;
|
|
|
3
3
|
/** rel-path → content for one example, from the dev checkout's examples dir. */
|
|
4
4
|
export declare function collectExampleFromDisk(examplesDir: string, name: string): Promise<Map<string, string>>;
|
|
5
5
|
/**
|
|
6
|
-
* Structured rewrite for a scaffolded
|
|
7
|
-
* runnable workspaces in-repo
|
|
6
|
+
* Structured rewrite for a scaffolded template's package.json — examples stay
|
|
7
|
+
* runnable workspaces in-repo (and `empty` follows their convention), so no
|
|
8
|
+
* template carries __NAME__ placeholders: the name is set here and the
|
|
9
|
+
* workspace `*` ranges become the lockstep `^<version>`.
|
|
8
10
|
*/
|
|
9
11
|
export declare function rewriteExamplePackageJson(content: string, opts: {
|
|
10
12
|
name: string;
|
package/dist/examples.js
CHANGED
|
@@ -33,8 +33,10 @@ export async function collectExampleFromDisk(examplesDir, name) {
|
|
|
33
33
|
return files;
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
* Structured rewrite for a scaffolded
|
|
37
|
-
* runnable workspaces in-repo
|
|
36
|
+
* Structured rewrite for a scaffolded template's package.json — examples stay
|
|
37
|
+
* runnable workspaces in-repo (and `empty` follows their convention), so no
|
|
38
|
+
* template carries __NAME__ placeholders: the name is set here and the
|
|
39
|
+
* workspace `*` ranges become the lockstep `^<version>`.
|
|
38
40
|
*/
|
|
39
41
|
export function rewriteExamplePackageJson(content, opts) {
|
|
40
42
|
const pkg = JSON.parse(content);
|
package/dist/flow.d.ts
CHANGED
|
@@ -97,10 +97,16 @@ export declare const EXAMPLE_QUESTION = "Select an example:";
|
|
|
97
97
|
export declare const MORE_EXAMPLES = "more-examples";
|
|
98
98
|
/** the examples menu's last row — back to the first menu */
|
|
99
99
|
export declare const BACK = "back";
|
|
100
|
-
/** The first menu: the
|
|
100
|
+
/** The first menu: the featured examples and `empty` in registry order, then the examples row. */
|
|
101
101
|
export declare function templateMenu(): PrompterOption[];
|
|
102
102
|
/** The second menu: the curated examples the first menu does not carry, in registry order, then Back. */
|
|
103
103
|
export declare function exampleMenu(): PrompterOption[];
|
|
104
|
+
/**
|
|
105
|
+
* The note a failed example fetch gets (interactive: the menu is asked
|
|
106
|
+
* again). Only `empty` ships inside the CLI; every other template is fetched
|
|
107
|
+
* from GitHub, so an offline machine can still scaffold that one.
|
|
108
|
+
*/
|
|
109
|
+
export declare const FETCH_FAILURE_HINT = "Only \"empty\" ships inside the CLI and needs no download \u2014 every other template is fetched from GitHub.";
|
|
104
110
|
export declare const PROVIDER_QUESTION = "Select an inference provider:";
|
|
105
111
|
/** `nola key`'s confirmation before the browser opens (the scaffold's consent is the provider select itself). */
|
|
106
112
|
export declare const SIGN_IN_QUESTION = "This machine already used its 25 free Nola runs. Sign in to get an API key for this project?";
|
|
@@ -132,7 +138,7 @@ export interface RunFlowArgs {
|
|
|
132
138
|
export declare const FLOW_OPTIONS: {
|
|
133
139
|
readonly template: {
|
|
134
140
|
readonly type: "string";
|
|
135
|
-
readonly description: "template to scaffold
|
|
141
|
+
readonly description: "template to scaffold: feature-extraction (the default), function-calling, agent-loop, typescript-interop, triage-ticket, empty, or another curated example";
|
|
136
142
|
};
|
|
137
143
|
readonly add: {
|
|
138
144
|
readonly type: "boolean";
|
package/dist/flow.js
CHANGED
|
@@ -18,7 +18,7 @@ import { nodeVersionWarning } from "./node-version.js";
|
|
|
18
18
|
import { openBrowser } from "./open-url.js";
|
|
19
19
|
import { detectPackageManager, packageManagerCommands } from "./package-manager.js";
|
|
20
20
|
import { isProviderId, PROVIDERS, providerById, providerIds } from "./providers.js";
|
|
21
|
-
import { entryFile, exampleNames, featuredNames,
|
|
21
|
+
import { entryFile, exampleNames, featuredNames, templateByName, templateNames } from "./registry.js";
|
|
22
22
|
import { ownVersion, scaffold, vendorEnvVar } from "./scaffold.js";
|
|
23
23
|
import { applyTrial } from "./trial.js";
|
|
24
24
|
const DEFAULT_DIR = "nola-app";
|
|
@@ -62,9 +62,10 @@ function templateOption(t) {
|
|
|
62
62
|
/*
|
|
63
63
|
* The template step is TWO menus (clack's select has no sections): the first
|
|
64
64
|
* lists the first-menu templates — feature-extraction, function-calling,
|
|
65
|
-
* typescript-interop, triage-ticket (the featured
|
|
66
|
-
* "More examples…" row; that row opens the
|
|
67
|
-
* Back row to return. `--template <name>`
|
|
65
|
+
* agent-loop, typescript-interop, triage-ticket (the featured examples),
|
|
66
|
+
* empty (the one builtin) — and one "More examples…" row; that row opens the
|
|
67
|
+
* remaining curated examples, with a Back row to return. `--template <name>`
|
|
68
|
+
* answers either level directly.
|
|
68
69
|
*/
|
|
69
70
|
export const TEMPLATE_QUESTION = "Select a template:";
|
|
70
71
|
export const EXAMPLE_QUESTION = "Select an example:";
|
|
@@ -72,11 +73,7 @@ export const EXAMPLE_QUESTION = "Select an example:";
|
|
|
72
73
|
export const MORE_EXAMPLES = "more-examples";
|
|
73
74
|
/** the examples menu's last row — back to the first menu */
|
|
74
75
|
export const BACK = "back";
|
|
75
|
-
/** `
|
|
76
|
-
const builtinNames = () => TEMPLATES.filter((t) => t.source === "builtin")
|
|
77
|
-
.map((t) => `"${t.name}"`)
|
|
78
|
-
.join(", ");
|
|
79
|
-
/** The first menu: the builtin templates and the featured example in registry order, then the examples row. */
|
|
76
|
+
/** The first menu: the featured examples and `empty` in registry order, then the examples row. */
|
|
80
77
|
export function templateMenu() {
|
|
81
78
|
const featured = featuredNames().map((name) => templateOption(templateByName(name)));
|
|
82
79
|
return [...featured, { value: MORE_EXAMPLES, label: "More examples…", hint: exampleNames().join(", ") }];
|
|
@@ -84,8 +81,14 @@ export function templateMenu() {
|
|
|
84
81
|
/** The second menu: the curated examples the first menu does not carry, in registry order, then Back. */
|
|
85
82
|
export function exampleMenu() {
|
|
86
83
|
const examples = exampleNames().map((name) => templateOption(templateByName(name)));
|
|
87
|
-
return [...examples, { value: BACK, label: "← Back", hint: "the
|
|
84
|
+
return [...examples, { value: BACK, label: "← Back", hint: "the first menu" }];
|
|
88
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* The note a failed example fetch gets (interactive: the menu is asked
|
|
88
|
+
* again). Only `empty` ships inside the CLI; every other template is fetched
|
|
89
|
+
* from GitHub, so an offline machine can still scaffold that one.
|
|
90
|
+
*/
|
|
91
|
+
export const FETCH_FAILURE_HINT = 'Only "empty" ships inside the CLI and needs no download — every other template is fetched from GitHub.';
|
|
89
92
|
/** Ask the two-level template menu until a template is chosen; null = cancelled. */
|
|
90
93
|
async function selectTemplate(prompter) {
|
|
91
94
|
for (;;) {
|
|
@@ -370,7 +373,7 @@ export function plainPrompter() {
|
|
|
370
373
|
export const FLOW_OPTIONS = {
|
|
371
374
|
template: {
|
|
372
375
|
type: "string",
|
|
373
|
-
description: "template to scaffold
|
|
376
|
+
description: "template to scaffold: feature-extraction (the default), function-calling, agent-loop, typescript-interop, triage-ticket, empty, or another curated example",
|
|
374
377
|
},
|
|
375
378
|
add: { type: "boolean", description: "add Nola to the existing project in [dir] instead of scaffolding" },
|
|
376
379
|
ide: { type: "string", description: "editor setup: vscode (the default) | none" },
|
|
@@ -675,7 +678,7 @@ export async function runFlow(args, opts = {}) {
|
|
|
675
678
|
}
|
|
676
679
|
catch (err) {
|
|
677
680
|
if (err instanceof ExampleFetchError && interactive) {
|
|
678
|
-
prompter.note(`${err.message}\
|
|
681
|
+
prompter.note(`${err.message}\n${FETCH_FAILURE_HINT}`);
|
|
679
682
|
input.dir = outcome.dir; // keep the chosen dir, re-pick the template
|
|
680
683
|
input.template = undefined;
|
|
681
684
|
continue;
|
package/dist/github.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Network/acquisition failure — the builtin
|
|
1
|
+
/** Network/acquisition failure — `empty`, the one builtin template, always works offline. */
|
|
2
2
|
export declare class ExampleFetchError extends Error {
|
|
3
3
|
}
|
|
4
4
|
export type FetchLike = (url: string) => Promise<{
|
package/dist/github.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Production example acquisition: GitHub Trees API + raw file fetches. */
|
|
2
2
|
const REPO = "nola-lang/nola";
|
|
3
|
-
/** Network/acquisition failure — the builtin
|
|
3
|
+
/** Network/acquisition failure — `empty`, the one builtin template, always works offline. */
|
|
4
4
|
export class ExampleFetchError extends Error {
|
|
5
5
|
}
|
|
6
6
|
/** Try the lockstep release tag first; a repo without that tag serves main. */
|
package/dist/index.js
CHANGED
package/dist/launch.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface Launcher {
|
|
|
17
17
|
/**
|
|
18
18
|
* `code <dir> [<dir>/<entry>]`, detached; "not-found" when VS Code's `code`
|
|
19
19
|
* command is not on PATH. `entry` is a project-relative file to open as the
|
|
20
|
-
* active editor on top of the folder (the
|
|
20
|
+
* active editor on top of the folder (the template's entry file).
|
|
21
21
|
*/
|
|
22
22
|
openVscode(dir: string, entry?: string): Promise<"opened" | "not-found">;
|
|
23
23
|
}
|
|
@@ -25,7 +25,7 @@ export interface Launcher {
|
|
|
25
25
|
* The arguments that open a project in VS Code: the folder alone, or the
|
|
26
26
|
* folder plus one file. `code <folder> <file>` opens the folder as the
|
|
27
27
|
* workspace and the file as the active editor — that is how the scaffold
|
|
28
|
-
* lands the user on
|
|
28
|
+
* lands the user on the entry file instead of an empty window.
|
|
29
29
|
*/
|
|
30
30
|
export declare function vscodeArgs(dir: string, entry?: string): string[];
|
|
31
31
|
/**
|
package/dist/launch.js
CHANGED
|
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
* The arguments that open a project in VS Code: the folder alone, or the
|
|
6
6
|
* folder plus one file. `code <folder> <file>` opens the folder as the
|
|
7
7
|
* workspace and the file as the active editor — that is how the scaffold
|
|
8
|
-
* lands the user on
|
|
8
|
+
* lands the user on the entry file instead of an empty window.
|
|
9
9
|
*/
|
|
10
10
|
export function vscodeArgs(dir, entry) {
|
|
11
11
|
return entry === undefined ? [dir] : [dir, join(dir, entry)];
|
package/dist/main.js
CHANGED
package/dist/node-version.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The Node floor the docs state. Plain `.ts` (
|
|
3
|
-
* is loaded by Node's native type
|
|
2
|
+
* The Node floor the docs state. Plain `.ts` (an example's `src/main.ts`, or the
|
|
3
|
+
* plain modules a `.tsi` entry imports) is loaded by Node's native type
|
|
4
|
+
* stripping, which is on by default only from
|
|
4
5
|
* 22.18 on the 22 line and 23.6 on the 23 line; before that it sits behind
|
|
5
6
|
* `--experimental-strip-types` and the default loader rejects `.ts` outright
|
|
6
7
|
* (ERR_UNKNOWN_FILE_EXTENSION) — `npm start` and the F5 launch both die on
|
package/dist/node-version.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The Node floor the docs state. Plain `.ts` (
|
|
3
|
-
* is loaded by Node's native type
|
|
2
|
+
* The Node floor the docs state. Plain `.ts` (an example's `src/main.ts`, or the
|
|
3
|
+
* plain modules a `.tsi` entry imports) is loaded by Node's native type
|
|
4
|
+
* stripping, which is on by default only from
|
|
4
5
|
* 22.18 on the 22 line and 23.6 on the 23 line; before that it sits behind
|
|
5
6
|
* `--experimental-strip-types` and the default loader rejects `.ts` outright
|
|
6
7
|
* (ERR_UNKNOWN_FILE_EXTENSION) — `npm start` and the F5 launch both die on
|
|
@@ -47,7 +48,7 @@ export function nodeVersionWarning(version = process.versions.node) {
|
|
|
47
48
|
return undefined;
|
|
48
49
|
const lines = [
|
|
49
50
|
`Node ${version} is older than Nola needs: Node >= ${NODE_FLOOR}, the first release that runs .ts files natively.`,
|
|
50
|
-
"On this Node, `npm start` and the VS Code F5 launch fail with ERR_UNKNOWN_FILE_EXTENSION
|
|
51
|
+
"On this Node, `npm start` and the VS Code F5 launch fail with ERR_UNKNOWN_FILE_EXTENSION on the first plain .ts file they load.",
|
|
51
52
|
];
|
|
52
53
|
if (hasStripTypesFlag(version)) {
|
|
53
54
|
lines.push("Upgrade Node (recommended), or set NODE_OPTIONS=--experimental-strip-types for `npm start` and add", '"--experimental-strip-types" to runtimeArgs in .vscode/launch.json.');
|
package/dist/providers.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* template registry: `nola` is the platform, labelled "nola: dev" so the row
|
|
5
5
|
* reads as a mode of the project rather than a fourth vendor (the free trial, or a key on the
|
|
6
6
|
* account — see `keyPath`), the four vendors write a bring-your-own config,
|
|
7
|
-
* `none` is the offline default (the
|
|
7
|
+
* `none` is the offline default (the template's own config: a recorded ledger or a mock). typesafe.ai
|
|
8
8
|
* serves literal unions and booleans only; it is listed for every template all
|
|
9
9
|
* the same, and `providerOptions` (flow.ts) brackets that caveat into its hint.
|
|
10
10
|
*/
|
package/dist/providers.js
CHANGED
|
@@ -10,7 +10,7 @@ export const PROVIDERS = [
|
|
|
10
10
|
},
|
|
11
11
|
{ id: "google", label: "Gemini", hint: 'google("gemini-2.5-flash"), reads GEMINI_API_KEY', envVar: "GEMINI_API_KEY", model: 'google("gemini-2.5-flash")' },
|
|
12
12
|
{ id: "typesafe", label: "typesafe.ai", hint: "typesafe(), reads TYPESAFE_API_KEY", envVar: "TYPESAFE_API_KEY", model: "typesafe()" },
|
|
13
|
-
{ id: "none", label: "Skip for now", hint: "the
|
|
13
|
+
{ id: "none", label: "Skip for now", hint: "keeps the template's own config (the recorded ones run offline); pick a model in nola.config.ts later" },
|
|
14
14
|
];
|
|
15
15
|
export function providerById(id) {
|
|
16
16
|
return PROVIDERS.find((p) => p.id === id);
|
package/dist/registry.d.ts
CHANGED
|
@@ -2,31 +2,40 @@
|
|
|
2
2
|
* The template menu. Static by design: it is versioned in lockstep with the
|
|
3
3
|
* git tag the example fetcher pulls from, so names and labels cannot drift.
|
|
4
4
|
*
|
|
5
|
+
* `empty` is the ONLY builtin template (config + tsconfig + a stub entry,
|
|
6
|
+
* shipped inside this package). Every other template IS a curated example:
|
|
7
|
+
* its files come from `examples/` — on disk in a dev checkout, from GitHub
|
|
8
|
+
* (the lockstep release tag) in production — and the scaffolder adds the
|
|
9
|
+
* project name, the lockstep ranges, the recommended `.gitignore`, the
|
|
10
|
+
* chosen provider's config and the next-steps comment on top.
|
|
11
|
+
*
|
|
5
12
|
* The first menu leads (in this order) and is organized BY FEATURE:
|
|
6
13
|
* `feature-extraction` (the default) and `function-calling` are each one
|
|
7
14
|
* `.tsi` file with a top-level ask — the two things Nola does —
|
|
15
|
+
* `agent-loop` is the same shape around a top-level `while`,
|
|
8
16
|
* `typescript-interop` is the infer-function shape called from plain
|
|
9
|
-
* TypeScript, `triage-ticket`
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
17
|
+
* TypeScript, `triage-ticket` the one-file shape on a non-chat vendor, then
|
|
18
|
+
* `empty` (a stub `src/main.tsi` to write your own in). The other curated
|
|
19
|
+
* examples follow (behind "More examples…").
|
|
20
|
+
* extract-person is deliberately absent — typescript-interop IS
|
|
21
|
+
* extract-person plus the replay ledger.
|
|
14
22
|
*/
|
|
15
23
|
export interface TemplateDef {
|
|
16
24
|
name: string;
|
|
17
25
|
/** one-line menu description */
|
|
18
26
|
label: string;
|
|
27
|
+
/** where the files come from: this package's `templates/` dir, or `examples/` */
|
|
19
28
|
source: "builtin" | "example";
|
|
20
29
|
/**
|
|
21
|
-
* An example listed on the FIRST menu, in registry order beside
|
|
22
|
-
*
|
|
23
|
-
*
|
|
30
|
+
* An example listed on the FIRST menu, in registry order beside `empty`,
|
|
31
|
+
* instead of behind "More examples…". Its files still come from examples/
|
|
32
|
+
* (`source` is unchanged).
|
|
24
33
|
*/
|
|
25
34
|
featured?: true;
|
|
26
35
|
/**
|
|
27
36
|
* The file that IS the program — what `start` runs, launch.json's
|
|
28
37
|
* `program`, what VS Code opens, where the next-steps comment lands.
|
|
29
|
-
* Absent = the plain-TS `src/main.ts`
|
|
38
|
+
* Absent = the plain-TS `src/main.ts` the other examples have.
|
|
30
39
|
*/
|
|
31
40
|
entry?: string;
|
|
32
41
|
/**
|
|
@@ -43,7 +52,7 @@ export interface TemplateDef {
|
|
|
43
52
|
/** What a template runs unless it names its own entry: the plain-TS main. */
|
|
44
53
|
export declare const DEFAULT_ENTRY = "src/main.ts";
|
|
45
54
|
export declare const TEMPLATES: readonly TemplateDef[];
|
|
46
|
-
/** The first menu's templates: the
|
|
55
|
+
/** The first menu's templates: the featured examples and `empty`, in registry order. */
|
|
47
56
|
export declare function featuredNames(): string[];
|
|
48
57
|
/** The examples behind "More examples…": every example the first menu does not carry. */
|
|
49
58
|
export declare function exampleNames(): string[];
|
package/dist/registry.js
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
/** What a template runs unless it names its own entry: the plain-TS main. */
|
|
2
2
|
export const DEFAULT_ENTRY = "src/main.ts";
|
|
3
3
|
export const TEMPLATES = [
|
|
4
|
-
{
|
|
4
|
+
{
|
|
5
|
+
name: "feature-extraction",
|
|
6
|
+
label: "extract typed data from context",
|
|
7
|
+
source: "example",
|
|
8
|
+
featured: true,
|
|
9
|
+
entry: "src/main.tsi",
|
|
10
|
+
},
|
|
5
11
|
{
|
|
6
12
|
name: "function-calling",
|
|
7
13
|
label: "call an async function from a TypeScript file",
|
|
8
|
-
source: "
|
|
14
|
+
source: "example",
|
|
15
|
+
featured: true,
|
|
16
|
+
entry: "src/main.tsi",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "agent-loop",
|
|
20
|
+
label: "an agent loop in one .tsi file: a top-level while with an ask, and a context statement read on every pass",
|
|
21
|
+
source: "example",
|
|
22
|
+
featured: true,
|
|
9
23
|
entry: "src/main.tsi",
|
|
10
24
|
},
|
|
11
25
|
{
|
|
12
26
|
name: "typescript-interop",
|
|
13
27
|
label: "an infer function example, imported and awaited from plain TypeScript",
|
|
14
|
-
source: "
|
|
28
|
+
source: "example",
|
|
29
|
+
featured: true,
|
|
15
30
|
},
|
|
16
31
|
{
|
|
17
32
|
name: "triage-ticket",
|
|
@@ -21,7 +36,7 @@ export const TEMPLATES = [
|
|
|
21
36
|
entry: "src/main.tsi",
|
|
22
37
|
provider: { label: "typesafe.ai", envVar: "TYPESAFE_API_KEY" },
|
|
23
38
|
},
|
|
24
|
-
{ name: "empty", label: "nola.config + tsconfig only, bring your own code", source: "builtin" },
|
|
39
|
+
{ name: "empty", label: "nola.config + tsconfig only, bring your own code", source: "builtin", entry: "src/main.tsi" },
|
|
25
40
|
{ name: "file-ticket", label: "call intents: the model fills a function's arguments", source: "example" },
|
|
26
41
|
{ name: "extract-resume", label: "nested arrays of objects, JSDoc schema descriptions", source: "example" },
|
|
27
42
|
{ name: "extract-invoice", label: "same-file type references, optional fields", source: "example" },
|
|
@@ -29,7 +44,7 @@ export const TEMPLATES = [
|
|
|
29
44
|
{ name: "chain-of-thought", label: "two asks sharing accumulating context", source: "example" },
|
|
30
45
|
{ name: "research-notes", label: "TS control flow orchestrating nola functions", source: "example" },
|
|
31
46
|
];
|
|
32
|
-
/** The first menu's templates: the
|
|
47
|
+
/** The first menu's templates: the featured examples and `empty`, in registry order. */
|
|
33
48
|
export function featuredNames() {
|
|
34
49
|
return TEMPLATES.filter((t) => t.source === "builtin" || t.featured).map((t) => t.name);
|
|
35
50
|
}
|