create-nola-lang 0.1.11 → 0.1.13
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 +19 -13
- package/dist/agents.d.ts +13 -1
- package/dist/agents.js +107 -9
- package/dist/checkout.d.ts +20 -0
- package/dist/checkout.js +48 -1
- package/dist/{chunk-MAULRZZD.js → chunk-LUSVIOAN.js} +248 -77
- package/dist/flow.d.ts +19 -5
- package/dist/flow.js +139 -51
- package/dist/ide.d.ts +2 -2
- package/dist/ide.js +12 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -1
- package/dist/launch.d.ts +2 -0
- package/dist/launch.js +1 -0
- package/dist/main.js +1 -1
- package/dist/providers.d.ts +7 -4
- package/dist/providers.js +3 -2
- package/dist/registry.d.ts +30 -2
- package/dist/registry.js +34 -7
- package/dist/scaffold.d.ts +20 -6
- package/dist/scaffold.js +42 -13
- package/package.json +1 -1
- package/skills/nola/SKILL.md +8 -5
- package/skills/nola/references/config.md +22 -13
- package/skills/nola/references/patterns.md +92 -17
- package/skills/nola/references/pitfalls.md +66 -19
- package/skills/nola/references/syntax.md +98 -28
- package/templates/_providers/typesafe.config.ts +18 -0
- package/templates/feature-extraction/README.md +20 -0
- package/templates/feature-extraction/nola.config.ts +14 -0
- package/templates/feature-extraction/nola.replay.jsonl +2 -0
- package/templates/feature-extraction/package.json +22 -0
- package/templates/feature-extraction/src/main.tsi +22 -0
- package/templates/function-calling/README.md +21 -0
- package/templates/function-calling/nola.config.ts +14 -0
- package/templates/function-calling/nola.replay.jsonl +1 -0
- package/templates/function-calling/package.json +22 -0
- package/templates/function-calling/src/main.tsi +12 -0
- package/templates/function-calling/src/tickets.ts +17 -0
- package/templates/function-calling/tsconfig.json +12 -0
- package/templates/{starter → typescript-interop}/nola.config.ts +1 -1
- package/templates/{starter → typescript-interop}/src/person.tsi +1 -1
- package/templates/typescript-interop/tsconfig.json +12 -0
- /package/templates/{starter → feature-extraction}/tsconfig.json +0 -0
- /package/templates/{starter → typescript-interop}/README.md +0 -0
- /package/templates/{starter → typescript-interop}/nola.replay.jsonl +0 -0
- /package/templates/{starter → typescript-interop}/package.json +0 -0
- /package/templates/{starter → typescript-interop}/src/main.ts +0 -0
package/dist/flow.d.ts
CHANGED
|
@@ -91,6 +91,16 @@ export declare const EDITOR_QUESTION = "Set up your Editor?";
|
|
|
91
91
|
export declare const AGENTS_QUESTION = "Set up Coding Agents?";
|
|
92
92
|
/** The grouped list a yes opens. */
|
|
93
93
|
export declare const SETUP_LIST_QUESTION = "What to set up? (Space toggles, Enter confirms)";
|
|
94
|
+
export declare const TEMPLATE_QUESTION = "Select a template:";
|
|
95
|
+
export declare const EXAMPLE_QUESTION = "Select an example:";
|
|
96
|
+
/** the first menu's last row — opens the examples menu */
|
|
97
|
+
export declare const MORE_EXAMPLES = "more-examples";
|
|
98
|
+
/** the examples menu's last row — back to the first menu */
|
|
99
|
+
export declare const BACK = "back";
|
|
100
|
+
/** The first menu: the builtin templates and the featured example in registry order, then the examples row. */
|
|
101
|
+
export declare function templateMenu(): PrompterOption[];
|
|
102
|
+
/** The second menu: the curated examples the first menu does not carry, in registry order, then Back. */
|
|
103
|
+
export declare function exampleMenu(): PrompterOption[];
|
|
94
104
|
export declare const PROVIDER_QUESTION = "Select an inference provider:";
|
|
95
105
|
/** `nola key`'s confirmation before the browser opens (the scaffold's consent is the provider select itself). */
|
|
96
106
|
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?";
|
|
@@ -100,8 +110,8 @@ export declare const SIGN_IN_QUESTION = "This machine already used its 25 free N
|
|
|
100
110
|
* machine used them, a key on the account when already signed in.
|
|
101
111
|
*/
|
|
102
112
|
export declare function nolaHint(path: KeyPath): string;
|
|
103
|
-
/** The provider menu with the nola row worded for this machine. */
|
|
104
|
-
export declare function providerOptions(path: KeyPath): PrompterOption[];
|
|
113
|
+
/** The provider menu with the nola row worded for this machine and the typesafe row for this template. */
|
|
114
|
+
export declare function providerOptions(path: KeyPath, template?: string): PrompterOption[];
|
|
105
115
|
/** Args fill prompts; whatever is missing is asked (never asked non-interactively). */
|
|
106
116
|
export declare function resolveScaffoldOptions(input: FlowInput, prompter: Prompter): Promise<FlowOutcome>;
|
|
107
117
|
/** Non-interactive prompter: output only; being asked anything is a bug. */
|
|
@@ -122,7 +132,7 @@ export interface RunFlowArgs {
|
|
|
122
132
|
export declare const FLOW_OPTIONS: {
|
|
123
133
|
readonly template: {
|
|
124
134
|
readonly type: "string";
|
|
125
|
-
readonly description: "template to scaffold (
|
|
135
|
+
readonly description: "template to scaffold (feature-extraction, function-calling, typescript-interop, empty, or a curated example)";
|
|
126
136
|
};
|
|
127
137
|
readonly add: {
|
|
128
138
|
readonly type: "boolean";
|
|
@@ -130,11 +140,11 @@ export declare const FLOW_OPTIONS: {
|
|
|
130
140
|
};
|
|
131
141
|
readonly ide: {
|
|
132
142
|
readonly type: "string";
|
|
133
|
-
readonly description: "editor setup: vscode | none";
|
|
143
|
+
readonly description: "editor setup: vscode (the default) | none";
|
|
134
144
|
};
|
|
135
145
|
readonly agents: {
|
|
136
146
|
readonly type: "string";
|
|
137
|
-
readonly description: `agent skill files: ${string} | all | none`;
|
|
147
|
+
readonly description: `agent skill files: ${string} | all | none (default: claude,universal)`;
|
|
138
148
|
};
|
|
139
149
|
readonly provider: {
|
|
140
150
|
readonly type: "string";
|
|
@@ -173,6 +183,10 @@ export interface RunFlowOptions {
|
|
|
173
183
|
}
|
|
174
184
|
/** The last non-empty output line, colour codes stripped, trimmed to one spinner line. */
|
|
175
185
|
export declare function lastOutputLine(output: string): string | undefined;
|
|
186
|
+
/** The last question when VS Code's `code` command is on PATH… */
|
|
187
|
+
export declare const INSTALL_OPEN_QUESTION = "Install dependencies and open VS Code?";
|
|
188
|
+
/** …and when it is not (2026-09-18): the editor is neither promised nor opened. */
|
|
189
|
+
export declare const INSTALL_QUESTION = "Install dependencies?";
|
|
176
190
|
/**
|
|
177
191
|
* A Retry-After wait in words. The per-address daily limit answers with the
|
|
178
192
|
* seconds left in the day ("49832 s" — nobody reads that as fourteen hours),
|
package/dist/flow.js
CHANGED
|
@@ -6,7 +6,7 @@ import { addNola } from "./add.js";
|
|
|
6
6
|
import { AGENT_IDS, AGENT_OPTIONS, defaultAgents, parseAgentsFlag, writeAgentSkills } from "./agents.js";
|
|
7
7
|
import { NolaApiError, nolaApiUrl } from "./api.js";
|
|
8
8
|
import { signIn } from "./auth.js";
|
|
9
|
-
import { LINK_CHECKOUT_ENV, linkCheckoutFromEnv, linkCheckoutPackages } from "./checkout.js";
|
|
9
|
+
import { CHECKOUT_DIST_SKIP_GLOB, LINK_CHECKOUT_ENV, linkCheckoutFromEnv, linkCheckoutPackages, skipCheckoutDistInLaunch, } from "./checkout.js";
|
|
10
10
|
import { defineCommand } from "./cli.js";
|
|
11
11
|
import { readSession } from "./credentials.js";
|
|
12
12
|
import { ExampleFetchError } from "./github.js";
|
|
@@ -18,23 +18,32 @@ 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 { TEMPLATES, templateByName, templateNames } from "./registry.js";
|
|
22
|
-
import { ownVersion, scaffold } from "./scaffold.js";
|
|
21
|
+
import { entryFile, exampleNames, featuredNames, TEMPLATES, templateByName, templateNames } from "./registry.js";
|
|
22
|
+
import { ownVersion, scaffold, vendorEnvVar } from "./scaffold.js";
|
|
23
23
|
import { applyTrial } from "./trial.js";
|
|
24
24
|
const DEFAULT_DIR = "nola-app";
|
|
25
|
-
const DEFAULT_TEMPLATE = "
|
|
25
|
+
const DEFAULT_TEMPLATE = "feature-extraction";
|
|
26
26
|
/*
|
|
27
27
|
* The wizard, in order (spec 2026-08-12-interactive-init-design.md, reordered
|
|
28
28
|
* 2026-09-03, provider select 2026-09-08): name → template → "Select an
|
|
29
29
|
* inference provider" (Nola first — the free runs, or a key on the account —
|
|
30
|
-
* then the vendors, then skip) →
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
30
|
+
* then the vendors, then skip) → then — once the files are on disk —
|
|
31
|
+
* "Install dependencies and open VS Code?". The editor setup and the agent
|
|
32
|
+
* skill are written WITHOUT a question (2026-09-18; `--ide none` /
|
|
33
|
+
* `--agents …` override): the "Set up your Editor and Coding Agents?" gate
|
|
34
|
+
* and its grouped list are kept behind `SETUP_STEP_ASKED` should the step
|
|
35
|
+
* come back. Every question is answered before anything is written; the
|
|
36
|
+
* nola key request runs after the last question.
|
|
36
37
|
*/
|
|
37
38
|
export const NAME_QUESTION = `Project name (press Enter to keep "${DEFAULT_DIR}"):`;
|
|
39
|
+
/**
|
|
40
|
+
* The retired setup step (2026-09-18). false = the editor setup and the agent
|
|
41
|
+
* skill are defaults (`DEFAULT_IDE`, `defaultAgents`) and nothing is asked;
|
|
42
|
+
* flip to true to bring the gate + grouped list back exactly as they were.
|
|
43
|
+
*/
|
|
44
|
+
const SETUP_STEP_ASKED = false;
|
|
45
|
+
/** The editor the scaffold sets up unless `--ide none` says otherwise. */
|
|
46
|
+
const DEFAULT_IDE = "vscode";
|
|
38
47
|
/** The yes/no gate in front of the setup list; narrowed when a flag already answered one half. */
|
|
39
48
|
export const SETUP_QUESTION = "Set up your Editor and Coding Agents?";
|
|
40
49
|
export const EDITOR_QUESTION = "Set up your Editor?";
|
|
@@ -46,25 +55,69 @@ const EDITOR_OPTIONS = [
|
|
|
46
55
|
{ value: "vscode", label: "VS Code", hint: ".vscode/ with a debug launch config + extension recommendation" },
|
|
47
56
|
];
|
|
48
57
|
function templateOption(t) {
|
|
49
|
-
// a pinned template names its vendor in the row itself: `
|
|
58
|
+
// a pinned template names its vendor in the row itself: `triage-ticket (typesafe.ai)`
|
|
50
59
|
const vendor = t.provider ? ` (${t.provider.label})` : "";
|
|
51
|
-
return { value: t.name, label:
|
|
60
|
+
return { value: t.name, label: `${t.name}${vendor}`, hint: t.label };
|
|
61
|
+
}
|
|
62
|
+
/*
|
|
63
|
+
* The template step is TWO menus (clack's select has no sections): the first
|
|
64
|
+
* lists the first-menu templates — feature-extraction, function-calling,
|
|
65
|
+
* typescript-interop, triage-ticket (the featured example), empty — and one
|
|
66
|
+
* "More examples…" row; that row opens the remaining curated examples, with a
|
|
67
|
+
* Back row to return. `--template <name>` answers either level directly.
|
|
68
|
+
*/
|
|
69
|
+
export const TEMPLATE_QUESTION = "Select a template:";
|
|
70
|
+
export const EXAMPLE_QUESTION = "Select an example:";
|
|
71
|
+
/** the first menu's last row — opens the examples menu */
|
|
72
|
+
export const MORE_EXAMPLES = "more-examples";
|
|
73
|
+
/** the examples menu's last row — back to the first menu */
|
|
74
|
+
export const BACK = "back";
|
|
75
|
+
/** `"feature-extraction", "function-calling", …` — for the fetch-failure note. */
|
|
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. */
|
|
80
|
+
export function templateMenu() {
|
|
81
|
+
const featured = featuredNames().map((name) => templateOption(templateByName(name)));
|
|
82
|
+
return [...featured, { value: MORE_EXAMPLES, label: "More examples…", hint: exampleNames().join(", ") }];
|
|
83
|
+
}
|
|
84
|
+
/** The second menu: the curated examples the first menu does not carry, in registry order, then Back. */
|
|
85
|
+
export function exampleMenu() {
|
|
86
|
+
const examples = exampleNames().map((name) => templateOption(templateByName(name)));
|
|
87
|
+
return [...examples, { value: BACK, label: "← Back", hint: "the builtin templates" }];
|
|
88
|
+
}
|
|
89
|
+
/** Ask the two-level template menu until a template is chosen; null = cancelled. */
|
|
90
|
+
async function selectTemplate(prompter) {
|
|
91
|
+
for (;;) {
|
|
92
|
+
const choice = await prompter.select(TEMPLATE_QUESTION, templateMenu());
|
|
93
|
+
if (choice === null)
|
|
94
|
+
return null;
|
|
95
|
+
if (choice !== MORE_EXAMPLES)
|
|
96
|
+
return choice;
|
|
97
|
+
const example = await prompter.select(EXAMPLE_QUESTION, exampleMenu());
|
|
98
|
+
if (example === null)
|
|
99
|
+
return null;
|
|
100
|
+
if (example !== BACK)
|
|
101
|
+
return example;
|
|
102
|
+
}
|
|
52
103
|
}
|
|
53
104
|
/**
|
|
54
|
-
* The
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
105
|
+
* The editor + agents answer. Today (the step is retired): the flags, else the
|
|
106
|
+
* defaults — VS Code and both skill targets — interactive or not, so a scaffold
|
|
107
|
+
* carries `.vscode/` and the skill unless `--ide none` / `--agents none` opt
|
|
108
|
+
* out. With `SETUP_STEP_ASKED`: a yes/no gate (default yes), then editor +
|
|
109
|
+
* coding agents in one grouped checkbox list with the same defaults
|
|
110
|
+
* preselected; `--ide` / `--agents` each fill their half — a group a flag
|
|
111
|
+
* already answered is not shown and the gate narrows to the half that
|
|
112
|
+
* remains; a no leaves the asked halves at none. null = cancelled.
|
|
60
113
|
*/
|
|
61
114
|
async function resolveSetup(input, prompter, dir) {
|
|
62
115
|
const ide = input.ide === "vscode" || input.ide === "none" ? input.ide : undefined;
|
|
63
116
|
const agents = input.agents !== undefined ? parseAgentsFlag(input.agents) : undefined;
|
|
64
117
|
if (ide !== undefined && agents !== undefined)
|
|
65
118
|
return { ide, agents };
|
|
66
|
-
if (!input.interactive)
|
|
67
|
-
return { ide: ide ??
|
|
119
|
+
if (!input.interactive || !SETUP_STEP_ASKED)
|
|
120
|
+
return { ide: ide ?? DEFAULT_IDE, agents: agents ?? defaultAgents(dir) };
|
|
68
121
|
const groups = [];
|
|
69
122
|
const initial = [];
|
|
70
123
|
if (ide === undefined) {
|
|
@@ -107,9 +160,27 @@ export function nolaHint(path) {
|
|
|
107
160
|
return providerById("nola")?.hint ?? "";
|
|
108
161
|
}
|
|
109
162
|
}
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
|
|
163
|
+
/**
|
|
164
|
+
* The typesafe row's bracket: the vendor serves literal unions and booleans
|
|
165
|
+
* only, so for a template whose asks go further the hint says so by name
|
|
166
|
+
* (the template was chosen one question earlier); no template (add mode)
|
|
167
|
+
* gets the generic form; a template whose own config already names
|
|
168
|
+
* typesafe.ai needs no warning.
|
|
169
|
+
*/
|
|
170
|
+
function typesafeHint(base, template) {
|
|
171
|
+
if (template === undefined)
|
|
172
|
+
return `${base} (literal unions and booleans only)`;
|
|
173
|
+
if (templateByName(template)?.provider?.label === "typesafe.ai")
|
|
174
|
+
return base;
|
|
175
|
+
return `${base} (does not support every construct in ${template}: literal unions and booleans only)`;
|
|
176
|
+
}
|
|
177
|
+
/** The provider menu with the nola row worded for this machine and the typesafe row for this template. */
|
|
178
|
+
export function providerOptions(path, template) {
|
|
179
|
+
return PROVIDERS.map((p) => ({
|
|
180
|
+
value: p.id,
|
|
181
|
+
label: p.label,
|
|
182
|
+
hint: p.id === "nola" ? nolaHint(path) : p.id === "typesafe" ? typesafeHint(p.hint, template) : p.hint,
|
|
183
|
+
}));
|
|
113
184
|
}
|
|
114
185
|
/** The provider a flag already chose: `--provider`, else the `--trial` / `--no-trial` shorthands; throws when they disagree. */
|
|
115
186
|
function flaggedProvider(input) {
|
|
@@ -135,17 +206,18 @@ function flaggedProvider(input) {
|
|
|
135
206
|
* one Enter away, Ctrl+C cancels). The key itself is minted later, on the
|
|
136
207
|
* stored session, after every question — nothing is consumed by a cancel.
|
|
137
208
|
*/
|
|
138
|
-
async function resolveProvider(input, prompter,
|
|
209
|
+
async function resolveProvider(input, prompter, template) {
|
|
139
210
|
const flagged = flaggedProvider(input);
|
|
140
211
|
if (flagged !== undefined)
|
|
141
212
|
return flagged;
|
|
142
213
|
// A template whose own config names its vendor (triage-ticket → typesafe())
|
|
143
214
|
// is not asked: "none" keeps that config, and the outro names its env var.
|
|
215
|
+
const pinned = template !== undefined && templateByName(template)?.provider !== undefined;
|
|
144
216
|
if (pinned || !input.interactive)
|
|
145
217
|
return "none";
|
|
146
218
|
const path = input.keyPath ?? { kind: "trial" };
|
|
147
219
|
while (true) {
|
|
148
|
-
const choice = await prompter.select(PROVIDER_QUESTION, providerOptions(path));
|
|
220
|
+
const choice = await prompter.select(PROVIDER_QUESTION, providerOptions(path, template));
|
|
149
221
|
if (choice === null)
|
|
150
222
|
return null;
|
|
151
223
|
if (!isProviderId(choice))
|
|
@@ -156,8 +228,8 @@ async function resolveProvider(input, prompter, pinned = false) {
|
|
|
156
228
|
}
|
|
157
229
|
}
|
|
158
230
|
/** The provider and setup (editor + agents) questions, in that order, shared by every outcome site; null = cancelled. */
|
|
159
|
-
async function resolveExtras(input, prompter, dir,
|
|
160
|
-
const provider = await resolveProvider(input, prompter,
|
|
231
|
+
async function resolveExtras(input, prompter, dir, template) {
|
|
232
|
+
const provider = await resolveProvider(input, prompter, template);
|
|
161
233
|
if (provider === null)
|
|
162
234
|
return null;
|
|
163
235
|
const setup = await resolveSetup(input, prompter, dir);
|
|
@@ -261,13 +333,13 @@ export async function resolveScaffoldOptions(input, prompter) {
|
|
|
261
333
|
template = DEFAULT_TEMPLATE;
|
|
262
334
|
}
|
|
263
335
|
else {
|
|
264
|
-
const choice = await prompter
|
|
336
|
+
const choice = await selectTemplate(prompter);
|
|
265
337
|
if (choice === null)
|
|
266
338
|
return { kind: "cancelled" };
|
|
267
339
|
template = choice;
|
|
268
340
|
}
|
|
269
341
|
}
|
|
270
|
-
const extras = await resolveExtras(input, prompter, dir,
|
|
342
|
+
const extras = await resolveExtras(input, prompter, dir, template);
|
|
271
343
|
if (extras === null)
|
|
272
344
|
return { kind: "cancelled" };
|
|
273
345
|
return { kind: "scaffold", dir, template, force, ...extras };
|
|
@@ -296,10 +368,16 @@ export function plainPrompter() {
|
|
|
296
368
|
* `nola init`): parseArgs descriptors plus the help line each shows.
|
|
297
369
|
*/
|
|
298
370
|
export const FLOW_OPTIONS = {
|
|
299
|
-
template: {
|
|
371
|
+
template: {
|
|
372
|
+
type: "string",
|
|
373
|
+
description: "template to scaffold (feature-extraction, function-calling, typescript-interop, empty, or a curated example)",
|
|
374
|
+
},
|
|
300
375
|
add: { type: "boolean", description: "add Nola to the existing project in [dir] instead of scaffolding" },
|
|
301
|
-
ide: { type: "string", description: "editor setup: vscode | none" },
|
|
302
|
-
agents: {
|
|
376
|
+
ide: { type: "string", description: "editor setup: vscode (the default) | none" },
|
|
377
|
+
agents: {
|
|
378
|
+
type: "string",
|
|
379
|
+
description: `agent skill files: ${AGENT_IDS.join(",")} | all | none (default: claude,universal)`,
|
|
380
|
+
},
|
|
303
381
|
provider: {
|
|
304
382
|
type: "string",
|
|
305
383
|
description: `inference provider: ${providerIds().join(" | ")} (nola = 25 free runs; non-interactive default: none)`,
|
|
@@ -325,8 +403,7 @@ function commented(command, note) {
|
|
|
325
403
|
function nextSteps(outcome, fileCount, name, pm, installed = false, grant = null) {
|
|
326
404
|
const cmd = packageManagerCommands(pm);
|
|
327
405
|
// a chosen vendor's env var, else the one the template's own config reads (a pinned template under "none")
|
|
328
|
-
const vendorEnv =
|
|
329
|
-
(outcome.provider === "none" ? templateByName(outcome.template)?.provider?.envVar : undefined);
|
|
406
|
+
const vendorEnv = vendorEnvVar(outcome.template, outcome.provider);
|
|
330
407
|
const startNote = grant
|
|
331
408
|
? grant.source === "trial"
|
|
332
409
|
? "25 free Nola runs — key in .env"
|
|
@@ -346,8 +423,6 @@ function nextSteps(outcome, fileCount, name, pm, installed = false, grant = null
|
|
|
346
423
|
}
|
|
347
424
|
/** How many trailing lines of a failed install's output the note shows. */
|
|
348
425
|
const INSTALL_LOG_TAIL = 20;
|
|
349
|
-
/** Every template's entry (builtin and curated examples alike) — what launch.json runs and VS Code opens. */
|
|
350
|
-
const ENTRY_FILE = "src/main.ts";
|
|
351
426
|
/** The last non-empty output line, colour codes stripped, trimmed to one spinner line. */
|
|
352
427
|
export function lastOutputLine(output) {
|
|
353
428
|
const lines = stripVTControlCharacters(output).split(/\r?\n/);
|
|
@@ -358,18 +433,24 @@ export function lastOutputLine(output) {
|
|
|
358
433
|
}
|
|
359
434
|
return undefined;
|
|
360
435
|
}
|
|
436
|
+
/** The last question when VS Code's `code` command is on PATH… */
|
|
437
|
+
export const INSTALL_OPEN_QUESTION = "Install dependencies and open VS Code?";
|
|
438
|
+
/** …and when it is not (2026-09-18): the editor is neither promised nor opened. */
|
|
439
|
+
export const INSTALL_QUESTION = "Install dependencies?";
|
|
361
440
|
/**
|
|
362
|
-
* The optional last step: "Install dependencies and open VS Code?"
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
441
|
+
* The optional last step: "Install dependencies and open VS Code?" — or just
|
|
442
|
+
* "Install dependencies?" on a machine without VS Code's `code` command,
|
|
443
|
+
* which then opens nothing. Offered only when the scaffold is interactive AND
|
|
444
|
+
* an editor was chosen — the editor step is what makes opening it meaningful.
|
|
445
|
+
* A yes runs the package manager's install under a spinner (its output is
|
|
446
|
+
* captured, the latest line shown beneath the title, the tail printed only on
|
|
447
|
+
* failure) and then opens the project in VS Code; Ctrl+C here is a plain no
|
|
448
|
+
* (the project is already on disk). Returns whether the install succeeded so
|
|
449
|
+
* the outro can drop that line.
|
|
370
450
|
*/
|
|
371
|
-
async function offerInstallAndOpen(dir, pm, prompter, launcher, linkCheckout) {
|
|
372
|
-
const
|
|
451
|
+
async function offerInstallAndOpen(dir, entryPath, pm, prompter, launcher, linkCheckout) {
|
|
452
|
+
const vscode = launcher.hasVscode();
|
|
453
|
+
const yes = await prompter.confirm(vscode ? INSTALL_OPEN_QUESTION : INSTALL_QUESTION, true);
|
|
373
454
|
if (!yes)
|
|
374
455
|
return { installed: false };
|
|
375
456
|
const cmd = packageManagerCommands(pm);
|
|
@@ -390,8 +471,12 @@ async function offerInstallAndOpen(dir, pm, prompter, launcher, linkCheckout) {
|
|
|
390
471
|
// at that workspace build instead.
|
|
391
472
|
if (linkCheckout !== null) {
|
|
392
473
|
const linked = await linkCheckoutPackages(dir, linkCheckout);
|
|
474
|
+
// The linked runtime now lives outside node_modules, so the launch
|
|
475
|
+
// config must blackbox it too or F10 over the first ask runs to the end.
|
|
476
|
+
const skipped = await skipCheckoutDistInLaunch(dir);
|
|
393
477
|
prompter.note(`${LINK_CHECKOUT_ENV}: linked ${linked.join(", ")} to ${join(linkCheckout, "packages")} — ` +
|
|
394
|
-
`a later ${cmd.install} restores the registry copies.`
|
|
478
|
+
`a later ${cmd.install} restores the registry copies.` +
|
|
479
|
+
(skipped ? `\nAdded "${CHECKOUT_DIST_SKIP_GLOB}" to skipFiles in .vscode/launch.json so stepping stays in your code.` : ""));
|
|
395
480
|
}
|
|
396
481
|
}
|
|
397
482
|
else {
|
|
@@ -399,11 +484,14 @@ async function offerInstallAndOpen(dir, pm, prompter, launcher, linkCheckout) {
|
|
|
399
484
|
const tail = stripVTControlCharacters(output).trim().split(/\r?\n/).slice(-INSTALL_LOG_TAIL).join("\n");
|
|
400
485
|
prompter.note(`${tail ? `${tail}\n` : ""}Run ${cmd.install} yourself once the cause is fixed.`);
|
|
401
486
|
}
|
|
487
|
+
if (!vscode)
|
|
488
|
+
return { installed };
|
|
402
489
|
// Land on the entry file, not an empty window: it opens with the next
|
|
403
490
|
// steps (F5, breakpoints, the extension) as its first lines.
|
|
404
|
-
const entry = existsSync(join(dir,
|
|
491
|
+
const entry = existsSync(join(dir, entryPath)) ? entryPath : undefined;
|
|
405
492
|
const opened = await launcher.openVscode(dir, entry);
|
|
406
493
|
if (opened === "not-found") {
|
|
494
|
+
// `code` was on PATH a moment ago and is gone now — rare, but say so.
|
|
407
495
|
prompter.note("VS Code's `code` command is not on PATH, so the folder was not opened. " +
|
|
408
496
|
"In VS Code run \"Shell Command: Install 'code' command in PATH\" (macOS) or re-run the installer with \"Add to PATH\" (Windows), then open the folder from VS Code.");
|
|
409
497
|
}
|
|
@@ -563,7 +651,7 @@ export async function runFlow(args, opts = {}) {
|
|
|
563
651
|
}
|
|
564
652
|
let ideWrote = [];
|
|
565
653
|
if (outcome.ide === "vscode") {
|
|
566
|
-
const ide = await writeVscodeSetup(outcome.dir);
|
|
654
|
+
const ide = await writeVscodeSetup(outcome.dir, entryFile(outcome.template));
|
|
567
655
|
ideWrote = ide.wrote;
|
|
568
656
|
for (const note of ide.skipped)
|
|
569
657
|
prompter.note(note);
|
|
@@ -576,7 +664,7 @@ export async function runFlow(args, opts = {}) {
|
|
|
576
664
|
prompter.note(note);
|
|
577
665
|
}
|
|
578
666
|
const launch = interactive && outcome.ide !== "none"
|
|
579
|
-
? await offerInstallAndOpen(outcome.dir, pm, prompter, opts.launcher ?? realLauncher, opts.linkCheckout ?? linkCheckoutFromEnv())
|
|
667
|
+
? await offerInstallAndOpen(outcome.dir, entryFile(outcome.template), pm, prompter, opts.launcher ?? realLauncher, opts.linkCheckout ?? linkCheckoutFromEnv())
|
|
580
668
|
: { installed: false };
|
|
581
669
|
const message = nextSteps({ ...outcome, provider }, files.length + trialWrote.length + ideWrote.length + agWrote.length, basename(resolve(outcome.dir)), pm, launch.installed, grant);
|
|
582
670
|
if (prompter.outro)
|
|
@@ -587,7 +675,7 @@ export async function runFlow(args, opts = {}) {
|
|
|
587
675
|
}
|
|
588
676
|
catch (err) {
|
|
589
677
|
if (err instanceof ExampleFetchError && interactive) {
|
|
590
|
-
prompter.note(`${err.message}\nThe
|
|
678
|
+
prompter.note(`${err.message}\nThe builtin templates (${builtinNames()}) work offline.`);
|
|
591
679
|
input.dir = outcome.dir; // keep the chosen dir, re-pick the template
|
|
592
680
|
input.template = undefined;
|
|
593
681
|
continue;
|
package/dist/ide.d.ts
CHANGED
|
@@ -4,6 +4,6 @@ export interface IdeSetupResult {
|
|
|
4
4
|
/** human-readable notes about files left untouched */
|
|
5
5
|
skipped: string[];
|
|
6
6
|
}
|
|
7
|
-
/** Write .vscode/launch.json + extensions.json; never rewrites an existing file. */
|
|
8
|
-
export declare function writeVscodeSetup(targetDir: string): Promise<IdeSetupResult>;
|
|
7
|
+
/** Write .vscode/launch.json (running `entry`) + extensions.json; never rewrites an existing file. */
|
|
8
|
+
export declare function writeVscodeSetup(targetDir: string, entry?: string): Promise<IdeSetupResult>;
|
|
9
9
|
//# sourceMappingURL=ide.d.ts.map
|
package/dist/ide.js
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
+
import { DEFAULT_ENTRY } from "./registry.js";
|
|
4
5
|
/**
|
|
5
6
|
* The resolved form of the VS Code extension's own "Nola: Launch File"
|
|
6
|
-
* configuration snippet, pointed at the
|
|
7
|
+
* configuration snippet, pointed at the template's real entry (src/main.ts —
|
|
7
8
|
* plain .ts runs under modern Node's native type stripping; the loader
|
|
8
|
-
* handles .tsi
|
|
9
|
+
* handles .tsi — or a one-file template's src/main.tsi, which the loader runs as
|
|
10
|
+
* the entry module). resolveSourceMapLocations and skipFiles are the two
|
|
9
11
|
* launch-config invariants AGENTS.md documents as mandatory for .tsi
|
|
10
12
|
* debugging — keep them.
|
|
11
13
|
*/
|
|
12
|
-
const
|
|
14
|
+
const launchJson = (entry) => ({
|
|
13
15
|
version: "0.2.0",
|
|
14
16
|
configurations: [
|
|
15
17
|
{
|
|
16
18
|
type: "node",
|
|
17
19
|
request: "launch",
|
|
18
20
|
name: "Nola: Launch main",
|
|
19
|
-
// ${workspaceFolder}
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
program: "${workspaceFolder}/src/main.ts",
|
|
21
|
+
// ${workspaceFolder} is VS Code's own launch-config variable — it must
|
|
22
|
+
// reach the file verbatim, hence the escaped dollar.
|
|
23
|
+
program: `\${workspaceFolder}/${entry}`,
|
|
23
24
|
runtimeArgs: ["--import", "nola-lang/register", "--enable-source-maps"],
|
|
24
25
|
console: "integratedTerminal",
|
|
25
26
|
// biome-ignore lint/suspicious/noTemplateCurlyInString: VS Code variable syntax
|
|
@@ -29,17 +30,17 @@ const LAUNCH_JSON = {
|
|
|
29
30
|
skipFiles: ["<node_internals>/**", "**/node_modules/**"],
|
|
30
31
|
},
|
|
31
32
|
],
|
|
32
|
-
};
|
|
33
|
+
});
|
|
33
34
|
/** publisher.name of packages/vscode — resolves once the extension is on the marketplace. */
|
|
34
35
|
const EXTENSIONS_JSON = { recommendations: ["nola.nola-vscode"] };
|
|
35
|
-
/** Write .vscode/launch.json + extensions.json; never rewrites an existing file. */
|
|
36
|
-
export async function writeVscodeSetup(targetDir) {
|
|
36
|
+
/** Write .vscode/launch.json (running `entry`) + extensions.json; never rewrites an existing file. */
|
|
37
|
+
export async function writeVscodeSetup(targetDir, entry = DEFAULT_ENTRY) {
|
|
37
38
|
const vscodeDir = join(resolve(targetDir), ".vscode");
|
|
38
39
|
await mkdir(vscodeDir, { recursive: true });
|
|
39
40
|
const wrote = [];
|
|
40
41
|
const skipped = [];
|
|
41
42
|
const files = [
|
|
42
|
-
["launch.json",
|
|
43
|
+
["launch.json", launchJson(entry)],
|
|
43
44
|
["extensions.json", EXTENSIONS_JSON],
|
|
44
45
|
];
|
|
45
46
|
for (const [name, content] of files) {
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type { Session } from "./credentials.js";
|
|
|
12
12
|
export { CREDENTIALS_FILE, credentialsPath, deleteSession, readSession, writeSession } from "./credentials.js";
|
|
13
13
|
export { collectExampleFromDisk, devExamplesDir, rewriteExamplePackageJson } from "./examples.js";
|
|
14
14
|
export type { FlowInput, FlowOutcome, KeyPath, Prompter, PrompterOption, RunFlowArgs, RunFlowOptions } from "./flow.js";
|
|
15
|
-
export { CREATE_COMMAND, FLOW_OPTIONS, nolaHint, PROVIDER_QUESTION, plainPrompter, providerOptions, resolveScaffoldOptions, runFlow, SIGN_IN_QUESTION } from "./flow.js";
|
|
15
|
+
export { CREATE_COMMAND, FLOW_OPTIONS, INSTALL_OPEN_QUESTION, INSTALL_QUESTION, nolaHint, PROVIDER_QUESTION, plainPrompter, providerOptions, resolveScaffoldOptions, runFlow, SIGN_IN_QUESTION, } from "./flow.js";
|
|
16
16
|
export type { FetchLike } from "./github.js";
|
|
17
17
|
export { ExampleFetchError, fetchExampleFromGitHub } from "./github.js";
|
|
18
18
|
export type { NolaHomeConfig, TrialAccountRecord } from "./home-config.js";
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
ExampleFetchError,
|
|
11
11
|
FLOW_OPTIONS,
|
|
12
12
|
HOME_CONFIG_FILE,
|
|
13
|
+
INSTALL_OPEN_QUESTION,
|
|
14
|
+
INSTALL_QUESTION,
|
|
13
15
|
NolaApiError,
|
|
14
16
|
PROVIDERS,
|
|
15
17
|
PROVIDER_QUESTION,
|
|
@@ -85,7 +87,7 @@ import {
|
|
|
85
87
|
writeEnvKey,
|
|
86
88
|
writeSession,
|
|
87
89
|
writeVscodeSetup
|
|
88
|
-
} from "./chunk-
|
|
90
|
+
} from "./chunk-LUSVIOAN.js";
|
|
89
91
|
import {
|
|
90
92
|
clackPrompter
|
|
91
93
|
} from "./chunk-7IQIRMK6.js";
|
|
@@ -144,6 +146,8 @@ export {
|
|
|
144
146
|
ExampleFetchError,
|
|
145
147
|
FLOW_OPTIONS,
|
|
146
148
|
HOME_CONFIG_FILE,
|
|
149
|
+
INSTALL_OPEN_QUESTION,
|
|
150
|
+
INSTALL_QUESTION,
|
|
147
151
|
NolaApiError,
|
|
148
152
|
PROVIDERS,
|
|
149
153
|
PROVIDER_QUESTION,
|
package/dist/launch.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ import type { PackageManager } from "./package-manager.js";
|
|
|
5
5
|
* calls; tests inject a recorder, the CLI uses `realLauncher`.
|
|
6
6
|
*/
|
|
7
7
|
export interface Launcher {
|
|
8
|
+
/** Is VS Code's `code` command on PATH? Checked BEFORE the question, so a machine without VS Code is never offered it. */
|
|
9
|
+
hasVscode(): boolean;
|
|
8
10
|
/**
|
|
9
11
|
* `<pm> install` in the project dir. Its output (stdout + stderr, as it
|
|
10
12
|
* arrives) goes to `onOutput` instead of the terminal, so the flow can keep
|
package/dist/launch.js
CHANGED
|
@@ -65,6 +65,7 @@ export function installArgs(pm) {
|
|
|
65
65
|
return pm === "npm" ? ["install", "--no-audit", "--no-fund"] : ["install"];
|
|
66
66
|
}
|
|
67
67
|
export const realLauncher = {
|
|
68
|
+
hasVscode: () => findOnPath("code") !== undefined,
|
|
68
69
|
install: (pm, dir, onOutput) => run(pm, installArgs(pm), dir, onOutput),
|
|
69
70
|
async openVscode(dir, entry) {
|
|
70
71
|
const code = findOnPath("code");
|
package/dist/main.js
CHANGED
package/dist/providers.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The inference-provider menu the wizard shows right after the template
|
|
3
3
|
* (2026-09-08, replacing the yes/no trial question). Static, like the
|
|
4
|
-
* template registry: `nola` is the platform
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
4
|
+
* template registry: `nola` is the platform, labelled "nola: dev" so the row
|
|
5
|
+
* reads as a mode of the project rather than a fourth vendor (the free trial, or a key on the
|
|
6
|
+
* account — see `keyPath`), the four vendors write a bring-your-own config,
|
|
7
|
+
* `none` is the offline default (the builtin templates replay their ledgers). typesafe.ai
|
|
8
|
+
* serves literal unions and booleans only; it is listed for every template all
|
|
9
|
+
* the same, and `providerOptions` (flow.ts) brackets that caveat into its hint.
|
|
7
10
|
*/
|
|
8
|
-
export type ProviderId = "nola" | "openai" | "anthropic" | "google" | "none";
|
|
11
|
+
export type ProviderId = "nola" | "openai" | "anthropic" | "google" | "typesafe" | "none";
|
|
9
12
|
export interface ProviderDef {
|
|
10
13
|
id: ProviderId;
|
|
11
14
|
/** menu label */
|
package/dist/providers.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const PROVIDERS = [
|
|
2
|
-
{ id: "nola", label: "
|
|
2
|
+
{ id: "nola", label: "nola: dev", hint: "25 free hosted runs, no API key required, suited for dev experiments", model: '"nola"' },
|
|
3
3
|
{ id: "openai", label: "OpenAI", hint: 'openai("gpt-5-mini"), reads OPENAI_API_KEY', envVar: "OPENAI_API_KEY", model: 'openai("gpt-5-mini")' },
|
|
4
4
|
{
|
|
5
5
|
id: "anthropic",
|
|
@@ -9,7 +9,8 @@ export const PROVIDERS = [
|
|
|
9
9
|
model: 'anthropic("claude-sonnet-4-5")',
|
|
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
|
-
{ id: "
|
|
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 builtin templates run offline from a replay ledger; pick a model in nola.config.ts later" },
|
|
13
14
|
];
|
|
14
15
|
export function providerById(id) {
|
|
15
16
|
return PROVIDERS.find((p) => p.id === id);
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
/**
|
|
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
|
-
*
|
|
5
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* The first menu leads (in this order) and is organized BY FEATURE:
|
|
6
|
+
* `feature-extraction` (the default) and `function-calling` are each one
|
|
7
|
+
* `.tsi` file with a top-level ask — the two things Nola does —
|
|
8
|
+
* `typescript-interop` is the infer-function shape called from plain
|
|
9
|
+
* TypeScript, `triage-ticket` (a FEATURED example: fetched from examples/
|
|
10
|
+
* like the rest, listed here because it is the one-file shape on a
|
|
11
|
+
* non-chat vendor) and `empty`. The other curated examples follow (behind
|
|
12
|
+
* "More examples…"). extract-person is deliberately absent —
|
|
13
|
+
* typescript-interop IS extract-person plus the replay ledger.
|
|
6
14
|
*/
|
|
7
15
|
export interface TemplateDef {
|
|
8
16
|
name: string;
|
|
9
17
|
/** one-line menu description */
|
|
10
18
|
label: string;
|
|
11
19
|
source: "builtin" | "example";
|
|
20
|
+
/**
|
|
21
|
+
* An example listed on the FIRST menu, in registry order beside the
|
|
22
|
+
* builtin templates, instead of behind "More examples…". Its files still
|
|
23
|
+
* come from examples/ (`source` is unchanged).
|
|
24
|
+
*/
|
|
25
|
+
featured?: true;
|
|
26
|
+
/**
|
|
27
|
+
* The file that IS the program — what `start` runs, launch.json's
|
|
28
|
+
* `program`, what VS Code opens, where the next-steps comment lands.
|
|
29
|
+
* Absent = the plain-TS `src/main.ts` every other template has.
|
|
30
|
+
*/
|
|
31
|
+
entry?: string;
|
|
12
32
|
/**
|
|
13
33
|
* The vendor the template's OWN config names. The flow skips the provider
|
|
14
34
|
* question for such a template (its config is the point of the example),
|
|
@@ -20,7 +40,15 @@ export interface TemplateDef {
|
|
|
20
40
|
envVar: string;
|
|
21
41
|
};
|
|
22
42
|
}
|
|
43
|
+
/** What a template runs unless it names its own entry: the plain-TS main. */
|
|
44
|
+
export declare const DEFAULT_ENTRY = "src/main.ts";
|
|
23
45
|
export declare const TEMPLATES: readonly TemplateDef[];
|
|
46
|
+
/** The first menu's templates: the builtins and the featured example, in registry order. */
|
|
47
|
+
export declare function featuredNames(): string[];
|
|
48
|
+
/** The examples behind "More examples…": every example the first menu does not carry. */
|
|
49
|
+
export declare function exampleNames(): string[];
|
|
24
50
|
export declare function templateByName(name: string): TemplateDef | undefined;
|
|
25
51
|
export declare function templateNames(): string[];
|
|
52
|
+
/** The template's entry file — what `start` runs, launch.json runs and VS Code opens. */
|
|
53
|
+
export declare function entryFile(template: string | undefined): string;
|
|
26
54
|
//# sourceMappingURL=registry.d.ts.map
|