create-nola-lang 0.1.11 → 0.1.12
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/README.md
CHANGED
|
@@ -10,17 +10,23 @@ npm start # runs offline via a committed replay ledger - no API key neede
|
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
Interactively the flow asks for a name (Enter keeps the default), a template,
|
|
13
|
-
an inference provider (
|
|
14
|
-
then OpenAI, Anthropic, Gemini,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
F5 to run, a breakpoint to debug, the recommended extension).
|
|
13
|
+
an inference provider (`nola: dev` first — 25 free hosted runs, no API key required, suited for dev experiments —
|
|
14
|
+
then OpenAI, Anthropic, Gemini, typesafe.ai — bracketed with the caveat that
|
|
15
|
+
it serves literal unions and booleans only — or skip), and — once the files are
|
|
16
|
+
written, unless `--ide none` — whether to install dependencies and open the
|
|
17
|
+
project in VS Code right away (just the install when VS Code's `code` command
|
|
18
|
+
is not on PATH; on the entry file — `src/main.tsi`, or
|
|
19
|
+
`src/main.ts` for `typescript-interop` — whose opening comment lists the next
|
|
20
|
+
steps: F5 to run, a breakpoint to debug, the recommended extension). The editor
|
|
21
|
+
setup (`.vscode/` with a debug config + the extension recommendation) and the
|
|
22
|
+
coding-agent skill (`.agents/skills/nola/`, with `.claude/skills/nola` a
|
|
23
|
+
symlink to it for Claude Code) are written without a question; `--ide none` and
|
|
24
|
+
`--agents none` opt out, `--agents claude,universal,agents-md` adds `AGENTS.md`.
|
|
21
25
|
|
|
22
|
-
You get
|
|
23
|
-
`
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
You get one template per feature — `feature-extraction` (the default) is one
|
|
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`, `typescript-interop` is
|
|
29
|
+
`src/person.tsi` plus a plain-TS consumer — with `nola.config.ts`, tsconfig,
|
|
30
|
+
and a recorded replay ledger so the first run works without any API key. `npm create nola`
|
|
31
|
+
is a short alias of this package (`create-nola`); `nola init` (from the
|
|
32
|
+
`nola-lang` package) lays down the same templates.
|
package/dist/agents.d.ts
CHANGED
|
@@ -9,6 +9,13 @@ export declare const SKILL_AGENTS_QUESTION = "Set up coding agents? (Space toggl
|
|
|
9
9
|
* that Cursor, Copilot, Codex, Gemini CLI and most other agents read.
|
|
10
10
|
*/
|
|
11
11
|
export declare const SKILL_DIRS: Readonly<Record<Exclude<AgentId, "agents-md">, string>>;
|
|
12
|
+
/**
|
|
13
|
+
* The Claude Code directory is a LINK to the universal one (2026-09-18): one
|
|
14
|
+
* copy of the content, `.claude/skills/nola → ../../.agents/skills/nola`,
|
|
15
|
+
* relative so the project can move. Only when both targets are written — a
|
|
16
|
+
* `claude`-only install has nothing to link to and stays a copy.
|
|
17
|
+
*/
|
|
18
|
+
export declare const CLAUDE_LINK_TARGET: string;
|
|
12
19
|
/** Shared option list for every agents multiselect. */
|
|
13
20
|
export declare const AGENT_OPTIONS: {
|
|
14
21
|
value: AgentId;
|
|
@@ -30,7 +37,12 @@ export interface SkillSource {
|
|
|
30
37
|
export declare function readSkillSource(): Promise<SkillSource>;
|
|
31
38
|
/** The `## Nola` section for AGENTS.md — also the paste snippet when skipped. */
|
|
32
39
|
export declare function agentsSection(s: SkillSource, version: string): string;
|
|
33
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* What the scaffold writes when `--agents` is absent (the setup question is
|
|
42
|
+
* retired, 2026-09-18) and `nola skill install`'s preselection: both skill
|
|
43
|
+
* targets — the universal directory plus the Claude Code link — so every
|
|
44
|
+
* skills-aware agent is covered. AGENTS.md stays opt-in.
|
|
45
|
+
*/
|
|
34
46
|
export declare function defaultAgents(_targetDir: string): AgentId[];
|
|
35
47
|
/** "all" | "none" | comma list of ids; unknown ids throw listing valid values. */
|
|
36
48
|
export declare function parseAgentsFlag(value: string): AgentId[];
|
package/dist/agents.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import { lstat, mkdir, readdir, readFile, realpath, rm, symlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, posix, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { ownVersion } from "./scaffold.js";
|
|
6
6
|
export const AGENT_IDS = ["claude", "universal", "agents-md"];
|
|
@@ -16,6 +16,13 @@ export const SKILL_DIRS = {
|
|
|
16
16
|
claude: ".claude/skills/nola",
|
|
17
17
|
universal: ".agents/skills/nola",
|
|
18
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* The Claude Code directory is a LINK to the universal one (2026-09-18): one
|
|
21
|
+
* copy of the content, `.claude/skills/nola → ../../.agents/skills/nola`,
|
|
22
|
+
* relative so the project can move. Only when both targets are written — a
|
|
23
|
+
* `claude`-only install has nothing to link to and stays a copy.
|
|
24
|
+
*/
|
|
25
|
+
export const CLAUDE_LINK_TARGET = posix.relative(posix.dirname(SKILL_DIRS.claude), SKILL_DIRS.universal);
|
|
19
26
|
/** Shared option list for every agents multiselect. */
|
|
20
27
|
export const AGENT_OPTIONS = [
|
|
21
28
|
{ value: "claude", label: "Claude Code", hint: ".claude/skills/nola/ (the only directory Claude Code reads)" },
|
|
@@ -63,7 +70,12 @@ export function agentsSection(s, version) {
|
|
|
63
70
|
function agentsMd(s, version) {
|
|
64
71
|
return `# AGENTS.md\n\n${agentsSection(s, version)}`;
|
|
65
72
|
}
|
|
66
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* What the scaffold writes when `--agents` is absent (the setup question is
|
|
75
|
+
* retired, 2026-09-18) and `nola skill install`'s preselection: both skill
|
|
76
|
+
* targets — the universal directory plus the Claude Code link — so every
|
|
77
|
+
* skills-aware agent is covered. AGENTS.md stays opt-in.
|
|
78
|
+
*/
|
|
67
79
|
export function defaultAgents(_targetDir) {
|
|
68
80
|
return ["claude", "universal"];
|
|
69
81
|
}
|
|
@@ -104,6 +116,69 @@ function disposition(path, existing, version, force) {
|
|
|
104
116
|
stale: true,
|
|
105
117
|
};
|
|
106
118
|
}
|
|
119
|
+
const untouched = (path) => ({
|
|
120
|
+
kind: "kept",
|
|
121
|
+
note: `${path} already exists and was not generated by nola — left untouched`,
|
|
122
|
+
stale: false,
|
|
123
|
+
});
|
|
124
|
+
/**
|
|
125
|
+
* Make `.claude/skills/nola` the link to `.agents/skills/nola`. What may
|
|
126
|
+
* already be there: the link itself (up to date); a link elsewhere or an
|
|
127
|
+
* unstamped copy (the user's, left alone); a stamped copy (reported, replaced
|
|
128
|
+
* under --force); a plain FILE holding the link text — what a checkout
|
|
129
|
+
* without symlink support (Windows, core.symlinks=false) makes of a committed
|
|
130
|
+
* link — or a dangling link, both ours to repair without --force. A directory
|
|
131
|
+
* symlink needs Developer Mode or admin on Windows: that failure is reported
|
|
132
|
+
* and the caller falls back to a copy.
|
|
133
|
+
*/
|
|
134
|
+
async function linkClaudeDir(root, version, force) {
|
|
135
|
+
const base = SKILL_DIRS.claude;
|
|
136
|
+
const link = join(root, ...base.split("/"));
|
|
137
|
+
const existing = await lstat(link).catch(() => undefined);
|
|
138
|
+
if (existing?.isSymbolicLink()) {
|
|
139
|
+
const target = join(root, ...SKILL_DIRS.universal.split("/"));
|
|
140
|
+
const [resolved, wanted] = await Promise.all([
|
|
141
|
+
realpath(link).catch(() => undefined),
|
|
142
|
+
realpath(target).catch(() => undefined),
|
|
143
|
+
]);
|
|
144
|
+
if (resolved !== undefined && resolved === wanted) {
|
|
145
|
+
return { kind: "kept", note: `${base} is a link to ${SKILL_DIRS.universal} — up to date`, stale: false };
|
|
146
|
+
}
|
|
147
|
+
if (resolved !== undefined)
|
|
148
|
+
return untouched(base);
|
|
149
|
+
await rm(link); // dangling: nothing behind it to keep
|
|
150
|
+
}
|
|
151
|
+
else if (existing?.isDirectory()) {
|
|
152
|
+
const marker = join(link, "SKILL.md");
|
|
153
|
+
const found = existsSync(marker) ? STAMP_RE.exec(await readFile(marker, "utf8")) : null;
|
|
154
|
+
if (!found)
|
|
155
|
+
return untouched(base);
|
|
156
|
+
if (!force) {
|
|
157
|
+
const note = found[1] === version
|
|
158
|
+
? `${base} is a copy (v${version}) — re-run with --force to replace it with a link to ${SKILL_DIRS.universal}`
|
|
159
|
+
: `${base}/SKILL.md is stale (v${found[1]} → v${version}) — re-run with --force to replace it`;
|
|
160
|
+
return { kind: "kept", note, stale: true };
|
|
161
|
+
}
|
|
162
|
+
await rm(link, { recursive: true });
|
|
163
|
+
}
|
|
164
|
+
else if (existing?.isFile()) {
|
|
165
|
+
if ((await readFile(link, "utf8")).trim() !== CLAUDE_LINK_TARGET)
|
|
166
|
+
return untouched(base);
|
|
167
|
+
await rm(link);
|
|
168
|
+
}
|
|
169
|
+
else if (existing) {
|
|
170
|
+
return untouched(base);
|
|
171
|
+
}
|
|
172
|
+
await mkdir(dirname(link), { recursive: true });
|
|
173
|
+
try {
|
|
174
|
+
await symlink(CLAUDE_LINK_TARGET, link, "dir");
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
const code = err.code ?? "error";
|
|
178
|
+
return { kind: "unlinkable", note: `could not link ${base} → ${SKILL_DIRS.universal} (${code}) — wrote a copy instead` };
|
|
179
|
+
}
|
|
180
|
+
return { kind: "linked" };
|
|
181
|
+
}
|
|
107
182
|
/** Copy the canonical skill dir verbatim (stamp aside) into `<root>/<base>/`. */
|
|
108
183
|
async function writeSkillDir(root, base, s, version) {
|
|
109
184
|
const dir = join(root, ...base.split("/"));
|
|
@@ -130,20 +205,43 @@ export async function writeAgentSkills(targetDir, agents, opts = {}) {
|
|
|
130
205
|
const removed = [];
|
|
131
206
|
const skipped = [];
|
|
132
207
|
let stale = false;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
const marker = join(root, ...base.split("/"), "SKILL.md");
|
|
208
|
+
/** A real copy at `<base>` — classified first, never blindly overwritten. */
|
|
209
|
+
const copy = async (base) => {
|
|
210
|
+
const dir = join(root, ...base.split("/"));
|
|
211
|
+
const marker = join(dir, "SKILL.md");
|
|
138
212
|
if (existsSync(marker)) {
|
|
139
213
|
const d = disposition(`${base}/SKILL.md`, await readFile(marker, "utf8"), version, force);
|
|
140
214
|
if (!d.write) {
|
|
141
215
|
skipped.push(d.note);
|
|
142
216
|
stale ||= d.stale;
|
|
143
|
-
|
|
217
|
+
return;
|
|
144
218
|
}
|
|
145
219
|
}
|
|
220
|
+
else if ((await lstat(dir).catch(() => undefined))?.isSymbolicLink()) {
|
|
221
|
+
await rm(dir); // a dangling link — nothing to keep, and mkdir would follow it
|
|
222
|
+
}
|
|
146
223
|
wrote.push(...(await writeSkillDir(root, base, source, version)));
|
|
224
|
+
};
|
|
225
|
+
// universal first: it is the one real copy the claude link points at
|
|
226
|
+
if (agents.includes("universal"))
|
|
227
|
+
await copy(SKILL_DIRS.universal);
|
|
228
|
+
if (agents.includes("claude")) {
|
|
229
|
+
if (!agents.includes("universal")) {
|
|
230
|
+
await copy(SKILL_DIRS.claude);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
const link = await linkClaudeDir(root, version, force);
|
|
234
|
+
if (link.kind === "linked") {
|
|
235
|
+
wrote.push(`${SKILL_DIRS.claude} → ${SKILL_DIRS.universal}`);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
skipped.push(link.note);
|
|
239
|
+
if (link.kind === "kept")
|
|
240
|
+
stale ||= link.stale;
|
|
241
|
+
else
|
|
242
|
+
await copy(SKILL_DIRS.claude);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
147
245
|
}
|
|
148
246
|
if (agents.includes("universal")) {
|
|
149
247
|
// Stamped legacy adapters are ours: superseded by .agents/skills, removed under --force.
|
package/dist/checkout.d.ts
CHANGED
|
@@ -21,4 +21,24 @@ export declare const CHECKOUT_LINKED_PACKAGES: readonly string[];
|
|
|
21
21
|
* Returns the linked package names.
|
|
22
22
|
*/
|
|
23
23
|
export declare function linkCheckoutPackages(dir: string, root: string): Promise<string[]>;
|
|
24
|
+
/**
|
|
25
|
+
* The skipFiles glob the in-repo dogfood launch configs carry. Node resolves
|
|
26
|
+
* a junction to its real path, so a linked scaffold runs the runtime from
|
|
27
|
+
* <checkout>/packages/\*\/dist — outside node_modules, which is all the
|
|
28
|
+
* scaffolded launch.json skips. Left unskipped, js-debug loses F10 over the
|
|
29
|
+
* process's first network ask: V8's step-over of the top-level await stays a
|
|
30
|
+
* plain step, lands on js-debug's injected WebAssembly pause (undici compiling
|
|
31
|
+
* its HTTP parser for the first fetch), and js-debug resumes that pause
|
|
32
|
+
* without re-stepping, so the program runs to the end (VS Code trace,
|
|
33
|
+
* 2026-09-18). Blackboxing the dist restores the step.
|
|
34
|
+
*/
|
|
35
|
+
export declare const CHECKOUT_DIST_SKIP_GLOB = "**/packages/*/dist/**";
|
|
36
|
+
/**
|
|
37
|
+
* Dev mode only, after linkCheckoutPackages: add CHECKOUT_DIST_SKIP_GLOB to
|
|
38
|
+
* every configuration's skipFiles in the scaffold's .vscode/launch.json.
|
|
39
|
+
* Returns true when the file changed; false when there is no launch.json, it
|
|
40
|
+
* is not plain JSON (a hand-edited one with comments is the user's), or every
|
|
41
|
+
* configuration already carries the glob.
|
|
42
|
+
*/
|
|
43
|
+
export declare function skipCheckoutDistInLaunch(dir: string): Promise<boolean>;
|
|
24
44
|
//# sourceMappingURL=checkout.d.ts.map
|
package/dist/checkout.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir, readFile, rm, symlink } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
/**
|
|
@@ -62,4 +62,51 @@ export async function linkCheckoutPackages(dir, root) {
|
|
|
62
62
|
}
|
|
63
63
|
return linked;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* The skipFiles glob the in-repo dogfood launch configs carry. Node resolves
|
|
67
|
+
* a junction to its real path, so a linked scaffold runs the runtime from
|
|
68
|
+
* <checkout>/packages/\*\/dist — outside node_modules, which is all the
|
|
69
|
+
* scaffolded launch.json skips. Left unskipped, js-debug loses F10 over the
|
|
70
|
+
* process's first network ask: V8's step-over of the top-level await stays a
|
|
71
|
+
* plain step, lands on js-debug's injected WebAssembly pause (undici compiling
|
|
72
|
+
* its HTTP parser for the first fetch), and js-debug resumes that pause
|
|
73
|
+
* without re-stepping, so the program runs to the end (VS Code trace,
|
|
74
|
+
* 2026-09-18). Blackboxing the dist restores the step.
|
|
75
|
+
*/
|
|
76
|
+
export const CHECKOUT_DIST_SKIP_GLOB = "**/packages/*/dist/**";
|
|
77
|
+
/**
|
|
78
|
+
* Dev mode only, after linkCheckoutPackages: add CHECKOUT_DIST_SKIP_GLOB to
|
|
79
|
+
* every configuration's skipFiles in the scaffold's .vscode/launch.json.
|
|
80
|
+
* Returns true when the file changed; false when there is no launch.json, it
|
|
81
|
+
* is not plain JSON (a hand-edited one with comments is the user's), or every
|
|
82
|
+
* configuration already carries the glob.
|
|
83
|
+
*/
|
|
84
|
+
export async function skipCheckoutDistInLaunch(dir) {
|
|
85
|
+
const path = join(dir, ".vscode", "launch.json");
|
|
86
|
+
if (!existsSync(path))
|
|
87
|
+
return false;
|
|
88
|
+
let launch;
|
|
89
|
+
try {
|
|
90
|
+
launch = JSON.parse(await readFile(path, "utf8"));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (!Array.isArray(launch.configurations))
|
|
96
|
+
return false;
|
|
97
|
+
let changed = false;
|
|
98
|
+
for (const config of launch.configurations) {
|
|
99
|
+
if (typeof config !== "object" || config === null)
|
|
100
|
+
continue;
|
|
101
|
+
const skipFiles = Array.isArray(config.skipFiles) ? config.skipFiles : [];
|
|
102
|
+
if (skipFiles.includes(CHECKOUT_DIST_SKIP_GLOB))
|
|
103
|
+
continue;
|
|
104
|
+
config.skipFiles = [...skipFiles, CHECKOUT_DIST_SKIP_GLOB];
|
|
105
|
+
changed = true;
|
|
106
|
+
}
|
|
107
|
+
if (!changed)
|
|
108
|
+
return false;
|
|
109
|
+
await writeFile(path, `${JSON.stringify(launch, null, 2)}\n`);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
65
112
|
//# sourceMappingURL=checkout.js.map
|