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/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.
|
package/dist/add.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Runtime } from "./package-manager.js";
|
|
1
2
|
import { type ProviderId } from "./providers.js";
|
|
2
3
|
export interface AddResult {
|
|
3
4
|
/** files written, e.g. ["nola.config.ts", "package.json"] */
|
|
@@ -17,6 +18,8 @@ export interface AddOptions {
|
|
|
17
18
|
* empty template's — the nola key step rewrites it afterwards (`applyTrial`).
|
|
18
19
|
*/
|
|
19
20
|
provider?: ProviderId;
|
|
21
|
+
/** "bun" also merges @nola-lang/bun into devDependencies; the bunfig lines are suggested by the flow's outro, never written */
|
|
22
|
+
runtime?: Runtime;
|
|
20
23
|
}
|
|
21
24
|
/**
|
|
22
25
|
* Retrofit an existing npm project: write nola.config.ts (unless present) and
|
package/dist/add.js
CHANGED
|
@@ -61,7 +61,8 @@ export async function addNola(targetDir, opts = {}) {
|
|
|
61
61
|
await writeFile(configPath, await readFile(vendor ? providerConfigUrl(vendor.id) : EMPTY_CONFIG_URL, "utf8"));
|
|
62
62
|
wrote.push("nola.config.ts");
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
const required = opts.runtime === "bun" ? [...REQUIRED, ["devDependencies", "@nola-lang/bun", null]] : REQUIRED;
|
|
65
|
+
for (const [section, name, fixedRange] of required) {
|
|
65
66
|
const existing = existingRange(name);
|
|
66
67
|
if (existing !== undefined) {
|
|
67
68
|
skipped.push(`${name} already present (${existing}) — left untouched`);
|
package/dist/checkout.js
CHANGED
|
@@ -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);
|
|
@@ -52,6 +52,7 @@ async function checkoutRoot() {
|
|
|
52
52
|
var LINKS = [
|
|
53
53
|
["@nola-lang/runtime", "runtime"],
|
|
54
54
|
["@nola-lang/providers", "providers"],
|
|
55
|
+
["@nola-lang/bun", "bun"],
|
|
55
56
|
["nola-lang", "nola-lang"]
|
|
56
57
|
];
|
|
57
58
|
var LINK_CHECKOUT_ENV = "NOLA_LINK_CHECKOUT";
|
|
@@ -170,17 +171,39 @@ async function fetchExampleFromGitHub(name, version, fetchImpl = fetch) {
|
|
|
170
171
|
// src/registry.ts
|
|
171
172
|
var DEFAULT_ENTRY = "src/main.ts";
|
|
172
173
|
var TEMPLATES = [
|
|
173
|
-
{
|
|
174
|
+
{
|
|
175
|
+
name: "feature-extraction",
|
|
176
|
+
label: "extract typed data from context",
|
|
177
|
+
source: "example",
|
|
178
|
+
featured: true,
|
|
179
|
+
entry: "src/main.tsi"
|
|
180
|
+
},
|
|
174
181
|
{
|
|
175
182
|
name: "function-calling",
|
|
176
183
|
label: "call an async function from a TypeScript file",
|
|
177
|
-
source: "
|
|
184
|
+
source: "example",
|
|
185
|
+
featured: true,
|
|
186
|
+
entry: "src/main.tsi"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "tool-use",
|
|
190
|
+
label: "offer functions as tools: the model calls them as it needs before it answers",
|
|
191
|
+
source: "example",
|
|
192
|
+
featured: true,
|
|
193
|
+
entry: "src/main.tsi"
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "agent-loop",
|
|
197
|
+
label: "an agent loop in one .tsi file: a top-level while with an ask, and a context statement read on every pass",
|
|
198
|
+
source: "example",
|
|
199
|
+
featured: true,
|
|
178
200
|
entry: "src/main.tsi"
|
|
179
201
|
},
|
|
180
202
|
{
|
|
181
203
|
name: "typescript-interop",
|
|
182
204
|
label: "an infer function example, imported and awaited from plain TypeScript",
|
|
183
|
-
source: "
|
|
205
|
+
source: "example",
|
|
206
|
+
featured: true
|
|
184
207
|
},
|
|
185
208
|
{
|
|
186
209
|
name: "triage-ticket",
|
|
@@ -190,7 +213,7 @@ var TEMPLATES = [
|
|
|
190
213
|
entry: "src/main.tsi",
|
|
191
214
|
provider: { label: "typesafe.ai", envVar: "TYPESAFE_API_KEY" }
|
|
192
215
|
},
|
|
193
|
-
{ name: "empty", label: "nola.config + tsconfig only, bring your own code", source: "builtin" },
|
|
216
|
+
{ name: "empty", label: "nola.config + tsconfig only, bring your own code", source: "builtin", entry: "src/main.tsi" },
|
|
194
217
|
{ name: "file-ticket", label: "call intents: the model fills a function's arguments", source: "example" },
|
|
195
218
|
{ name: "extract-resume", label: "nested arrays of objects, JSDoc schema descriptions", source: "example" },
|
|
196
219
|
{ name: "extract-invoice", label: "same-file type references, optional fields", source: "example" },
|
|
@@ -231,19 +254,34 @@ function envExample(envVar) {
|
|
|
231
254
|
${envVar}=
|
|
232
255
|
`;
|
|
233
256
|
}
|
|
234
|
-
var
|
|
257
|
+
var BUNFIG_TOML = 'preload = ["@nola-lang/bun/preload"]\n\n[test]\npreload = ["@nola-lang/bun/preload"]\n';
|
|
258
|
+
var BUN_ENGINE = ">=1.3.0";
|
|
259
|
+
function applyBunRuntime(content, opts) {
|
|
260
|
+
const pkg = JSON.parse(content);
|
|
261
|
+
const scripts = pkg.scripts ?? {};
|
|
262
|
+
scripts.start = `bun ${opts.entry}`;
|
|
263
|
+
pkg.scripts = scripts;
|
|
264
|
+
const dev = pkg.devDependencies ?? {};
|
|
265
|
+
dev["@nola-lang/bun"] = `^${opts.version}`;
|
|
266
|
+
pkg.devDependencies = dev;
|
|
267
|
+
const engines = pkg.engines ?? {};
|
|
268
|
+
engines.bun = BUN_ENGINE;
|
|
269
|
+
pkg.engines = engines;
|
|
270
|
+
return `${JSON.stringify(pkg, null, 2)}
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
235
273
|
var GITIGNORE_URL = new URL("../templates/_gitignore", import.meta.url);
|
|
236
274
|
async function withRecommendedGitignore(files) {
|
|
237
275
|
if (!files.has(".gitignore")) files.set(".gitignore", await readFile3(GITIGNORE_URL, "utf8"));
|
|
238
276
|
return files;
|
|
239
277
|
}
|
|
240
|
-
|
|
241
|
-
function nextStepsComment(ide, template) {
|
|
278
|
+
function nextStepsComment(ide, template, runtime = "node") {
|
|
242
279
|
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";
|
|
280
|
+
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";
|
|
281
|
+
const runLine = runtime === "bun" ? "// 1. Run this file: bun start (F5 runs it too, under Node's loader \u2014 .vscode/launch.json is already set up)." : "// 1. Press F5 to run this file (.vscode/launch.json is already set up).";
|
|
244
282
|
return [
|
|
245
283
|
"// Next steps in VS Code:",
|
|
246
|
-
|
|
284
|
+
runLine,
|
|
247
285
|
`// 2. Set a breakpoint ${breakpointIn} and press F5 again to step through the ask.`,
|
|
248
286
|
'// 3. Install the recommended "Nola" extension when VS Code offers it \u2014 IntelliSense,',
|
|
249
287
|
"// go to definition and diagnostics inside .tsi files."
|
|
@@ -251,33 +289,11 @@ function nextStepsComment(ide, template) {
|
|
|
251
289
|
}
|
|
252
290
|
return [
|
|
253
291
|
"// Next steps:",
|
|
254
|
-
|
|
292
|
+
`// 1. Run this file: ${runtime === "bun" ? "bun start" : "npm start"}`,
|
|
255
293
|
"// 2. Set up your editor (VS Code extension, debugging): https://nola.sh/docs/start/editor-setup/"
|
|
256
294
|
].join("\n");
|
|
257
295
|
}
|
|
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
|
-
}
|
|
296
|
+
var OFFLINE_ONLY = ["nola.replay.jsonl"];
|
|
281
297
|
async function ownVersion() {
|
|
282
298
|
const pkg = JSON.parse(await readFile3(new URL("../package.json", import.meta.url), "utf8"));
|
|
283
299
|
return pkg.version;
|
|
@@ -289,67 +305,48 @@ async function prepareTarget(absRoot, force) {
|
|
|
289
305
|
if (!force) throw new Error(`target directory ${absRoot} is not empty`);
|
|
290
306
|
for (const entry of entries) await rm2(join3(absRoot, entry), { recursive: true, force: true });
|
|
291
307
|
}
|
|
308
|
+
async function collectTemplate(def, version) {
|
|
309
|
+
if (def.source === "builtin") return collectExampleFromDisk(TEMPLATES_DIR, def.name);
|
|
310
|
+
const dev = await devExamplesDir();
|
|
311
|
+
return dev ? collectExampleFromDisk(dev, def.name) : fetchExampleFromGitHub(def.name, version);
|
|
312
|
+
}
|
|
292
313
|
async function scaffold(targetDir, opts = {}) {
|
|
293
314
|
const root = targetDir;
|
|
294
315
|
const absRoot = resolve(targetDir);
|
|
295
316
|
const template = opts.template ?? "feature-extraction";
|
|
296
317
|
const def = templateByName(template);
|
|
297
318
|
if (!def) throw new Error(`unknown template "${template}" (valid: ${templateNames().join(", ")})`);
|
|
319
|
+
const version = await ownVersion();
|
|
320
|
+
const files = await collectTemplate(def, version);
|
|
298
321
|
await prepareTarget(absRoot, opts.force ?? false);
|
|
299
322
|
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
323
|
const provider = opts.provider ?? "none";
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
await
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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");
|
|
324
|
+
const runtime = opts.runtime ?? "node";
|
|
325
|
+
const entry = entryFile(template);
|
|
326
|
+
if (provider !== "none") {
|
|
327
|
+
for (const offline of OFFLINE_ONLY) files.delete(offline);
|
|
328
|
+
files.set("nola.config.ts", await readFile3(providerConfigUrl(provider), "utf8"));
|
|
329
|
+
}
|
|
330
|
+
const manifest = files.get("package.json");
|
|
331
|
+
if (manifest) files.set("package.json", rewriteExamplePackageJson(manifest, { name, version }));
|
|
332
|
+
if (runtime === "bun") {
|
|
333
|
+
files.set("bunfig.toml", BUNFIG_TOML);
|
|
334
|
+
const current = files.get("package.json");
|
|
335
|
+
if (current !== void 0) files.set("package.json", applyBunRuntime(current, { entry, version }));
|
|
336
|
+
}
|
|
337
|
+
await withRecommendedGitignore(files);
|
|
338
|
+
const envVar = vendorEnvVar(template, provider);
|
|
339
|
+
if (envVar !== void 0 && !files.has(".env.example")) files.set(".env.example", envExample(envVar));
|
|
340
|
+
const program = files.get(entry);
|
|
341
|
+
if (program !== void 0) files.set(entry, `${nextStepsComment(opts.ide ?? "none", template, runtime)}
|
|
342
|
+
|
|
343
|
+
${program}`);
|
|
344
|
+
for (const [relPath, content] of files) {
|
|
345
|
+
const target = join3(absRoot, relPath);
|
|
346
|
+
await mkdir2(join3(target, ".."), { recursive: true });
|
|
347
|
+
await writeFile2(target, content);
|
|
351
348
|
}
|
|
352
|
-
return { root, files: files.sort() };
|
|
349
|
+
return { root, files: [...files.keys()].sort() };
|
|
353
350
|
}
|
|
354
351
|
|
|
355
352
|
// src/add.ts
|
|
@@ -402,7 +399,8 @@ async function addNola(targetDir, opts = {}) {
|
|
|
402
399
|
await writeFile3(configPath, await readFile4(vendor ? providerConfigUrl(vendor.id) : EMPTY_CONFIG_URL, "utf8"));
|
|
403
400
|
wrote.push("nola.config.ts");
|
|
404
401
|
}
|
|
405
|
-
|
|
402
|
+
const required = opts.runtime === "bun" ? [...REQUIRED, ["devDependencies", "@nola-lang/bun", null]] : REQUIRED;
|
|
403
|
+
for (const [section, name, fixedRange] of required) {
|
|
406
404
|
const existing = existingRange(name);
|
|
407
405
|
if (existing !== void 0) {
|
|
408
406
|
skipped.push(`${name} already present (${existing}) \u2014 left untouched`);
|
|
@@ -1571,7 +1569,7 @@ function nodeVersionWarning(version = process.versions.node) {
|
|
|
1571
1569
|
if (nodeSupportsTypeStripping(version)) return void 0;
|
|
1572
1570
|
const lines = [
|
|
1573
1571
|
`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
|
|
1572
|
+
"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
1573
|
];
|
|
1576
1574
|
if (hasStripTypesFlag(version)) {
|
|
1577
1575
|
lines.push(
|
|
@@ -1592,6 +1590,12 @@ function detectPackageManager(userAgent = process.env.npm_config_user_agent) {
|
|
|
1592
1590
|
function packageManagerCommands(pm) {
|
|
1593
1591
|
return { install: `${pm} install`, start: `${pm} start`, run: `${pm} run` };
|
|
1594
1592
|
}
|
|
1593
|
+
function isRuntime(value) {
|
|
1594
|
+
return value === "node" || value === "bun";
|
|
1595
|
+
}
|
|
1596
|
+
function defaultRuntime(pm) {
|
|
1597
|
+
return pm === "bun" ? "bun" : "node";
|
|
1598
|
+
}
|
|
1595
1599
|
|
|
1596
1600
|
// src/flow.ts
|
|
1597
1601
|
var DEFAULT_DIR = "nola-app";
|
|
@@ -1614,15 +1618,15 @@ var TEMPLATE_QUESTION = "Select a template:";
|
|
|
1614
1618
|
var EXAMPLE_QUESTION = "Select an example:";
|
|
1615
1619
|
var MORE_EXAMPLES = "more-examples";
|
|
1616
1620
|
var BACK = "back";
|
|
1617
|
-
var builtinNames = () => TEMPLATES.filter((t) => t.source === "builtin").map((t) => `"${t.name}"`).join(", ");
|
|
1618
1621
|
function templateMenu() {
|
|
1619
1622
|
const featured = featuredNames().map((name) => templateOption(templateByName(name)));
|
|
1620
1623
|
return [...featured, { value: MORE_EXAMPLES, label: "More examples\u2026", hint: exampleNames().join(", ") }];
|
|
1621
1624
|
}
|
|
1622
1625
|
function exampleMenu() {
|
|
1623
1626
|
const examples = exampleNames().map((name) => templateOption(templateByName(name)));
|
|
1624
|
-
return [...examples, { value: BACK, label: "\u2190 Back", hint: "the
|
|
1627
|
+
return [...examples, { value: BACK, label: "\u2190 Back", hint: "the first menu" }];
|
|
1625
1628
|
}
|
|
1629
|
+
var FETCH_FAILURE_HINT = 'Only "empty" ships inside the CLI and needs no download \u2014 every other template is fetched from GitHub.';
|
|
1626
1630
|
async function selectTemplate(prompter) {
|
|
1627
1631
|
for (; ; ) {
|
|
1628
1632
|
const choice = await prompter.select(TEMPLATE_QUESTION, templateMenu());
|
|
@@ -1708,11 +1712,18 @@ async function resolveProvider(input, prompter, template) {
|
|
|
1708
1712
|
return choice;
|
|
1709
1713
|
}
|
|
1710
1714
|
}
|
|
1715
|
+
function resolveRuntime(input) {
|
|
1716
|
+
if (input.runtime !== void 0) {
|
|
1717
|
+
if (!isRuntime(input.runtime)) throw new Error(`invalid --runtime "${input.runtime}" (valid: node, bun)`);
|
|
1718
|
+
return input.runtime;
|
|
1719
|
+
}
|
|
1720
|
+
return defaultRuntime(input.packageManager ?? "npm");
|
|
1721
|
+
}
|
|
1711
1722
|
async function resolveExtras(input, prompter, dir, template) {
|
|
1712
1723
|
const provider = await resolveProvider(input, prompter, template);
|
|
1713
1724
|
if (provider === null) return null;
|
|
1714
1725
|
const setup = await resolveSetup(input, prompter, dir);
|
|
1715
|
-
return setup === null ? null : { provider, ...setup };
|
|
1726
|
+
return setup === null ? null : { provider, ...setup, runtime: resolveRuntime(input) };
|
|
1716
1727
|
}
|
|
1717
1728
|
async function resolveScaffoldOptions(input, prompter) {
|
|
1718
1729
|
if (input.add && input.template) {
|
|
@@ -1723,6 +1734,9 @@ async function resolveScaffoldOptions(input, prompter) {
|
|
|
1723
1734
|
if (input.ide !== void 0 && input.ide !== "vscode" && input.ide !== "none") {
|
|
1724
1735
|
throw new Error(`invalid --ide "${input.ide}" (valid: vscode, none)`);
|
|
1725
1736
|
}
|
|
1737
|
+
if (input.runtime !== void 0 && !isRuntime(input.runtime)) {
|
|
1738
|
+
throw new Error(`invalid --runtime "${input.runtime}" (valid: node, bun)`);
|
|
1739
|
+
}
|
|
1726
1740
|
if (input.agents !== void 0) parseAgentsFlag(input.agents);
|
|
1727
1741
|
flaggedProvider(input);
|
|
1728
1742
|
if (input.add) {
|
|
@@ -1832,10 +1846,14 @@ function plainPrompter() {
|
|
|
1832
1846
|
var FLOW_OPTIONS = {
|
|
1833
1847
|
template: {
|
|
1834
1848
|
type: "string",
|
|
1835
|
-
description: "template to scaffold
|
|
1849
|
+
description: "template to scaffold: feature-extraction (the default), function-calling, agent-loop, typescript-interop, triage-ticket, empty, or another curated example"
|
|
1836
1850
|
},
|
|
1837
1851
|
add: { type: "boolean", description: "add Nola to the existing project in [dir] instead of scaffolding" },
|
|
1838
1852
|
ide: { type: "string", description: "editor setup: vscode (the default) | none" },
|
|
1853
|
+
runtime: {
|
|
1854
|
+
type: "string",
|
|
1855
|
+
description: "runtime the project runs on: node | bun (default: bun when bun ran the scaffolder \u2014 bunfig.toml preload + `bun src/main.tsi`)"
|
|
1856
|
+
},
|
|
1839
1857
|
agents: {
|
|
1840
1858
|
type: "string",
|
|
1841
1859
|
description: `agent skill files: ${AGENT_IDS.join(",")} | all | none (default: claude,universal)`
|
|
@@ -1995,6 +2013,8 @@ async function runFlow(args, opts = {}) {
|
|
|
1995
2013
|
agents: args.agents,
|
|
1996
2014
|
provider: args.provider,
|
|
1997
2015
|
trial: args.trial,
|
|
2016
|
+
runtime: args.runtime,
|
|
2017
|
+
packageManager: pm,
|
|
1998
2018
|
keyPath,
|
|
1999
2019
|
signIn: signInNow,
|
|
2000
2020
|
cwd: opts.cwd,
|
|
@@ -2008,7 +2028,7 @@ async function runFlow(args, opts = {}) {
|
|
|
2008
2028
|
}
|
|
2009
2029
|
if (outcome.kind === "add") {
|
|
2010
2030
|
const grant = outcome.provider === "nola" ? await obtainTrial(prompter, opts, interactive) : null;
|
|
2011
|
-
const result = await addNola(outcome.dir, { provider: outcome.provider });
|
|
2031
|
+
const result = await addNola(outcome.dir, { provider: outcome.provider, runtime: outcome.runtime });
|
|
2012
2032
|
const trial = grant ? await applyTrial(outcome.dir, { apiKey: grant.apiKey, hasConfig: !result.wrote.includes("nola.config.ts") }) : { wrote: [], skipped: [] };
|
|
2013
2033
|
const ide = outcome.ide === "vscode" ? await writeVscodeSetup(outcome.dir) : { wrote: [], skipped: [] };
|
|
2014
2034
|
const ag = outcome.agents.length > 0 ? await writeAgentSkills(outcome.dir, outcome.agents) : { wrote: [], removed: [], skipped: [] };
|
|
@@ -2021,7 +2041,10 @@ async function runFlow(args, opts = {}) {
|
|
|
2021
2041
|
}
|
|
2022
2042
|
const lines = [
|
|
2023
2043
|
packageManagerCommands(pm).install,
|
|
2024
|
-
|
|
2044
|
+
...outcome.runtime === "bun" ? [
|
|
2045
|
+
'bunfig.toml: preload = ["@nola-lang/bun/preload"] (runs .tsi from source under Bun)',
|
|
2046
|
+
'optional start script: "start": "bun src/main.ts"'
|
|
2047
|
+
] : ['optional start script: "start": "nola run src/main.ts"'],
|
|
2025
2048
|
'tsconfig tip: directory-style include (e.g. ["src"]) lets the editor see .tsi files',
|
|
2026
2049
|
commented(CONSOLE_COMMAND, CONSOLE_NOTE)
|
|
2027
2050
|
];
|
|
@@ -2036,7 +2059,13 @@ Next steps:
|
|
|
2036
2059
|
try {
|
|
2037
2060
|
const grant = outcome.provider === "nola" ? await obtainTrial(prompter, opts, interactive) : null;
|
|
2038
2061
|
const provider = outcome.provider === "nola" ? grant ? "nola" : "none" : outcome.provider;
|
|
2039
|
-
const { files } = await scaffold(outcome.dir, {
|
|
2062
|
+
const { files } = await scaffold(outcome.dir, {
|
|
2063
|
+
template: outcome.template,
|
|
2064
|
+
force: outcome.force,
|
|
2065
|
+
provider,
|
|
2066
|
+
ide: outcome.ide,
|
|
2067
|
+
runtime: outcome.runtime
|
|
2068
|
+
});
|
|
2040
2069
|
let trialWrote = [];
|
|
2041
2070
|
if (grant) {
|
|
2042
2071
|
const trial = await applyTrial(outcome.dir, { apiKey: grant.apiKey, hasConfig: false });
|
|
@@ -2070,7 +2099,7 @@ Next steps:
|
|
|
2070
2099
|
} catch (err) {
|
|
2071
2100
|
if (err instanceof ExampleFetchError && interactive) {
|
|
2072
2101
|
prompter.note(`${err.message}
|
|
2073
|
-
|
|
2102
|
+
${FETCH_FAILURE_HINT}`);
|
|
2074
2103
|
input.dir = outcome.dir;
|
|
2075
2104
|
input.template = void 0;
|
|
2076
2105
|
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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type AgentId } from "./agents.js";
|
|
2
2
|
import { type CommandSpec } from "./cli.js";
|
|
3
3
|
import { type Launcher } from "./launch.js";
|
|
4
|
-
import { type PackageManager } from "./package-manager.js";
|
|
4
|
+
import { type PackageManager, type Runtime } from "./package-manager.js";
|
|
5
5
|
import { type ProviderId } from "./providers.js";
|
|
6
6
|
export interface PrompterOption {
|
|
7
7
|
value: string;
|
|
@@ -45,6 +45,10 @@ export interface FlowInput {
|
|
|
45
45
|
provider?: string;
|
|
46
46
|
/** --trial / --no-trial: shorthand for --provider nola / --provider none */
|
|
47
47
|
trial?: boolean;
|
|
48
|
+
/** --runtime <node|bun>; undefined = bun when bun ran the scaffolder, else node */
|
|
49
|
+
runtime?: string;
|
|
50
|
+
/** the manager that invoked us — decides the default runtime */
|
|
51
|
+
packageManager?: PackageManager;
|
|
48
52
|
/** how the nola row is worded, decided by runFlow from ~/.nola (spec 2026-09-04-cli-sign-in-design.md §5); default trial */
|
|
49
53
|
keyPath?: KeyPath;
|
|
50
54
|
/**
|
|
@@ -75,12 +79,14 @@ export type FlowOutcome = {
|
|
|
75
79
|
provider: ProviderId;
|
|
76
80
|
ide: "vscode" | "none";
|
|
77
81
|
agents: AgentId[];
|
|
82
|
+
runtime: Runtime;
|
|
78
83
|
} | {
|
|
79
84
|
kind: "add";
|
|
80
85
|
dir: string;
|
|
81
86
|
provider: ProviderId;
|
|
82
87
|
ide: "vscode" | "none";
|
|
83
88
|
agents: AgentId[];
|
|
89
|
+
runtime: Runtime;
|
|
84
90
|
} | {
|
|
85
91
|
kind: "cancelled";
|
|
86
92
|
};
|
|
@@ -97,10 +103,16 @@ export declare const EXAMPLE_QUESTION = "Select an example:";
|
|
|
97
103
|
export declare const MORE_EXAMPLES = "more-examples";
|
|
98
104
|
/** the examples menu's last row — back to the first menu */
|
|
99
105
|
export declare const BACK = "back";
|
|
100
|
-
/** The first menu: the
|
|
106
|
+
/** The first menu: the featured examples and `empty` in registry order, then the examples row. */
|
|
101
107
|
export declare function templateMenu(): PrompterOption[];
|
|
102
108
|
/** The second menu: the curated examples the first menu does not carry, in registry order, then Back. */
|
|
103
109
|
export declare function exampleMenu(): PrompterOption[];
|
|
110
|
+
/**
|
|
111
|
+
* The note a failed example fetch gets (interactive: the menu is asked
|
|
112
|
+
* again). Only `empty` ships inside the CLI; every other template is fetched
|
|
113
|
+
* from GitHub, so an offline machine can still scaffold that one.
|
|
114
|
+
*/
|
|
115
|
+
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
116
|
export declare const PROVIDER_QUESTION = "Select an inference provider:";
|
|
105
117
|
/** `nola key`'s confirmation before the browser opens (the scaffold's consent is the provider select itself). */
|
|
106
118
|
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?";
|
|
@@ -124,6 +136,7 @@ export interface RunFlowArgs {
|
|
|
124
136
|
agents?: string;
|
|
125
137
|
provider?: string;
|
|
126
138
|
trial?: boolean;
|
|
139
|
+
runtime?: string;
|
|
127
140
|
}
|
|
128
141
|
/**
|
|
129
142
|
* The flow's flags, declared ONCE for both bins (`npm create nola-lang` and
|
|
@@ -132,7 +145,7 @@ export interface RunFlowArgs {
|
|
|
132
145
|
export declare const FLOW_OPTIONS: {
|
|
133
146
|
readonly template: {
|
|
134
147
|
readonly type: "string";
|
|
135
|
-
readonly description: "template to scaffold
|
|
148
|
+
readonly description: "template to scaffold: feature-extraction (the default), function-calling, agent-loop, typescript-interop, triage-ticket, empty, or another curated example";
|
|
136
149
|
};
|
|
137
150
|
readonly add: {
|
|
138
151
|
readonly type: "boolean";
|
|
@@ -142,6 +155,10 @@ export declare const FLOW_OPTIONS: {
|
|
|
142
155
|
readonly type: "string";
|
|
143
156
|
readonly description: "editor setup: vscode (the default) | none";
|
|
144
157
|
};
|
|
158
|
+
readonly runtime: {
|
|
159
|
+
readonly type: "string";
|
|
160
|
+
readonly description: "runtime the project runs on: node | bun (default: bun when bun ran the scaffolder — bunfig.toml preload + `bun src/main.tsi`)";
|
|
161
|
+
};
|
|
145
162
|
readonly agents: {
|
|
146
163
|
readonly type: "string";
|
|
147
164
|
readonly description: `agent skill files: ${string} | all | none (default: claude,universal)`;
|