create-nola-lang 0.1.2 → 0.1.4
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 +3 -2
- package/dist/agents.d.ts +27 -2
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +136 -67
- package/dist/agents.js.map +1 -1
- package/dist/{chunk-QTJ64H2Y.js → chunk-VZM5EYBG.js} +138 -64
- package/dist/flow.d.ts +3 -0
- package/dist/flow.d.ts.map +1 -1
- package/dist/flow.js +9 -7
- package/dist/flow.js.map +1 -1
- package/dist/index.js +5 -3
- package/dist/main.js +1 -1
- package/dist/package-manager.d.ts +17 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +8 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/skill-install.d.ts +2 -0
- package/dist/skill-install.d.ts.map +1 -1
- package/dist/skill-install.js +6 -2
- package/dist/skill-install.js.map +1 -1
- package/package.json +7 -5
- package/skills/nola/SKILL.md +75 -0
- package/skills/nola/references/config.md +227 -0
- package/skills/nola/references/patterns.md +277 -0
- package/skills/nola/references/pitfalls.md +289 -0
- package/skills/nola/references/syntax.md +377 -0
- package/templates/starter/_gitignore +2 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Scaffolds a new [Nola](https://github.com/nola-lang/nola) project:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm create nola-lang my-app
|
|
6
|
+
npm create nola my-app # or: npm create nola-lang my-app
|
|
7
7
|
cd my-app
|
|
8
8
|
npm install
|
|
9
9
|
npm start # runs offline via a committed replay ledger - no API key needed
|
|
@@ -11,5 +11,6 @@ npm start # runs offline via a committed replay ledger - no API key neede
|
|
|
11
11
|
|
|
12
12
|
You get a typed extraction example (`src/person.tsi` + a plain-TS consumer),
|
|
13
13
|
`nola.config.ts`, tsconfig, and a recorded replay ledger so the first run
|
|
14
|
-
works without any API key. `nola
|
|
14
|
+
works without any API key. `npm create nola` is a short alias of this package
|
|
15
|
+
(`create-nola`); `nola init` (from the `nola-lang` package) lays
|
|
15
16
|
down the same starter.
|
package/dist/agents.d.ts
CHANGED
|
@@ -6,6 +6,22 @@ export declare const AGENT_OPTIONS: {
|
|
|
6
6
|
label: string;
|
|
7
7
|
hint: string;
|
|
8
8
|
}[];
|
|
9
|
+
/**
|
|
10
|
+
* Adapters that inject context inline embed SKILL.md's BODY verbatim, so no
|
|
11
|
+
* adapter restates the language rules in its own words and the four can
|
|
12
|
+
* never drift from each other (spec §2).
|
|
13
|
+
*/
|
|
14
|
+
export interface SkillSource {
|
|
15
|
+
/** the frontmatter block of SKILL.md, without the --- fences */
|
|
16
|
+
frontmatter: string;
|
|
17
|
+
/** the first heading up to (not including) `## References` */
|
|
18
|
+
body: string;
|
|
19
|
+
/** the whole file, verbatim — what the claude adapter copies */
|
|
20
|
+
full: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function readSkillSource(): Promise<SkillSource>;
|
|
23
|
+
/** The `## Nola` section for AGENTS.md — also the paste snippet when skipped. */
|
|
24
|
+
export declare function agentsSection(s: SkillSource, version: string): string;
|
|
9
25
|
/** Agents whose tooling is already present in the project. */
|
|
10
26
|
export declare function detectAgents(targetDir: string): AgentId[];
|
|
11
27
|
/** The interactive preselection: detected agents + the cross-agent baseline. */
|
|
@@ -17,7 +33,16 @@ export interface AgentSetupResult {
|
|
|
17
33
|
wrote: string[];
|
|
18
34
|
/** human-readable notes about files left untouched */
|
|
19
35
|
skipped: string[];
|
|
36
|
+
/** true when something was left stale — the caller can suggest --force */
|
|
37
|
+
stale: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface WriteAgentSkillsOptions {
|
|
40
|
+
/** replace files stamped by another version (never touches unstamped files) */
|
|
41
|
+
force?: boolean;
|
|
20
42
|
}
|
|
21
|
-
/**
|
|
22
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Write the selected adapters as self-contained, version-stamped content.
|
|
45
|
+
* Existing files are classified, never blindly overwritten (spec §3).
|
|
46
|
+
*/
|
|
47
|
+
export declare function writeAgentSkills(targetDir: string, agents: AgentId[], opts?: WriteAgentSkillsOptions): Promise<AgentSetupResult>;
|
|
23
48
|
//# sourceMappingURL=agents.d.ts.map
|
package/dist/agents.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;AAEpE,eAAO,MAAM,SAAS,EAAE,SAAS,OAAO,EAAiD,CAAC;AAE1F,wEAAwE;AACxE,eAAO,MAAM,aAAa,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAK1E,CAAC;AAiBF;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC,CAQ5D;AA6BD,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAErE;AAMD,8DAA8D;AAC9D,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,EAAE,CAOzD;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,EAAE,CAE1D;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,CAaxD;AAED,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,sDAAsD;IACtD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0EAA0E;IAC1E,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,+EAA+E;IAC/E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAyCD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,OAAO,EAAE,EACjB,IAAI,GAAE,uBAA4B,GACjC,OAAO,CAAC,gBAAgB,CAAC,CAsD3B"}
|
package/dist/agents.js
CHANGED
|
@@ -1,70 +1,68 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { ownVersion } from "./scaffold.js";
|
|
4
6
|
export const AGENT_IDS = ["claude", "cursor", "copilot", "agents-md"];
|
|
5
7
|
/** Shared option list for every "Set up coding agents?" multiselect. */
|
|
6
8
|
export const AGENT_OPTIONS = [
|
|
7
|
-
{ value: "agents-md", label: "AGENTS.md", hint: "cross-agent
|
|
8
|
-
{ value: "claude", label: "Claude Code", hint: ".claude/skills/nola/
|
|
9
|
+
{ value: "agents-md", label: "AGENTS.md", hint: "cross-agent instructions (Codex, Gemini CLI, …)" },
|
|
10
|
+
{ value: "claude", label: "Claude Code", hint: ".claude/skills/nola/ (full skill + references)" },
|
|
9
11
|
{ value: "cursor", label: "Cursor", hint: ".cursor/rules/nola.mdc" },
|
|
10
12
|
{ value: "copilot", label: "GitHub Copilot", hint: ".github/instructions/nola.instructions.md" },
|
|
11
13
|
];
|
|
12
14
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* (
|
|
15
|
+
* The canonical skill content, resolved relative to this module — `src/` in
|
|
16
|
+
* this checkout, `dist/` in the published tarball, both one level under the
|
|
17
|
+
* package root (the same trick TEMPLATES_DIR uses in scaffold.ts).
|
|
16
18
|
*/
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
cursor: { path: ".cursor/rules/nola.mdc", content: CURSOR_RULE },
|
|
65
|
-
copilot: { path: ".github/instructions/nola.instructions.md", content: COPILOT_INSTRUCTIONS },
|
|
66
|
-
"agents-md": { path: "AGENTS.md", content: AGENTS_MD },
|
|
67
|
-
};
|
|
19
|
+
const SKILLS_DIR = fileURLToPath(new URL("../skills/nola/", import.meta.url));
|
|
20
|
+
/** Marks a generated file so staleness is detectable (spec §3). */
|
|
21
|
+
const STAMP_RE = /^<!-- nola-skill v(\S+)/m;
|
|
22
|
+
const stamp = (version) => `<!-- nola-skill v${version} — regenerate with: nola skill install --force -->`;
|
|
23
|
+
/** Pointer-era adapters (0.1.0–0.1.3) named this path; they are superseded. */
|
|
24
|
+
const LEGACY_MARKER = "node_modules/nola-lang/skills/nola";
|
|
25
|
+
export async function readSkillSource() {
|
|
26
|
+
const full = (await readFile(join(SKILLS_DIR, "SKILL.md"), "utf8")).replaceAll("\r\n", "\n");
|
|
27
|
+
const fm = /^---\n([\s\S]*?)\n---\n/.exec(full);
|
|
28
|
+
if (!fm)
|
|
29
|
+
throw new Error("SKILL.md is missing its frontmatter block");
|
|
30
|
+
const afterFm = full.slice(fm[0].length);
|
|
31
|
+
const refsAt = afterFm.indexOf("\n## References");
|
|
32
|
+
const body = (refsAt === -1 ? afterFm : afterFm.slice(0, refsAt)).trim();
|
|
33
|
+
return { frontmatter: fm[1], body, full };
|
|
34
|
+
}
|
|
35
|
+
/** Points at the project-local copy — never node_modules (spec §1). */
|
|
36
|
+
const LOCAL_REFERENCES = [
|
|
37
|
+
"Deeper references, when this repo has them:",
|
|
38
|
+
"`.claude/skills/nola/references/` — syntax.md, patterns.md, config.md,",
|
|
39
|
+
"pitfalls.md.",
|
|
40
|
+
].join("\n");
|
|
41
|
+
function cursorRule(s, version) {
|
|
42
|
+
return [
|
|
43
|
+
"---",
|
|
44
|
+
"description: Nola (.tsi) language rules",
|
|
45
|
+
'globs: ["**/*.tsi", "nola.config.ts"]',
|
|
46
|
+
"alwaysApply: false",
|
|
47
|
+
"---",
|
|
48
|
+
stamp(version),
|
|
49
|
+
"",
|
|
50
|
+
s.body,
|
|
51
|
+
"",
|
|
52
|
+
LOCAL_REFERENCES,
|
|
53
|
+
"",
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
56
|
+
function copilotInstructions(s, version) {
|
|
57
|
+
return ["---", 'applyTo: "**/*.tsi"', "---", stamp(version), "", s.body, "", LOCAL_REFERENCES, ""].join("\n");
|
|
58
|
+
}
|
|
59
|
+
/** The `## Nola` section for AGENTS.md — also the paste snippet when skipped. */
|
|
60
|
+
export function agentsSection(s, version) {
|
|
61
|
+
return ["## Nola", "", stamp(version), "", s.body, "", LOCAL_REFERENCES, ""].join("\n");
|
|
62
|
+
}
|
|
63
|
+
function agentsMd(s, version) {
|
|
64
|
+
return `# AGENTS.md\n\n${agentsSection(s, version)}`;
|
|
65
|
+
}
|
|
68
66
|
/** Agents whose tooling is already present in the project. */
|
|
69
67
|
export function detectAgents(targetDir) {
|
|
70
68
|
const root = resolve(targetDir);
|
|
@@ -87,7 +85,10 @@ export function parseAgentsFlag(value) {
|
|
|
87
85
|
return [...AGENT_IDS];
|
|
88
86
|
if (value === "none")
|
|
89
87
|
return [];
|
|
90
|
-
const ids = value
|
|
88
|
+
const ids = value
|
|
89
|
+
.split(",")
|
|
90
|
+
.map((s) => s.trim())
|
|
91
|
+
.filter(Boolean);
|
|
91
92
|
for (const id of ids) {
|
|
92
93
|
if (!AGENT_IDS.includes(id)) {
|
|
93
94
|
throw new Error(`invalid --agents "${value}" (valid: ${AGENT_IDS.join(", ")} — or all, none)`);
|
|
@@ -95,27 +96,95 @@ export function parseAgentsFlag(value) {
|
|
|
95
96
|
}
|
|
96
97
|
return [...new Set(ids)];
|
|
97
98
|
}
|
|
98
|
-
/**
|
|
99
|
-
|
|
99
|
+
/** Classify an existing target per spec §3. */
|
|
100
|
+
function disposition(path, existing, version, force) {
|
|
101
|
+
const found = STAMP_RE.exec(existing);
|
|
102
|
+
// Legacy pointers are checked FIRST and independently of the stamp: the
|
|
103
|
+
// 0.1.0–0.1.3 adapters predate stamping, so they carry none at all.
|
|
104
|
+
const legacy = existing.includes(LEGACY_MARKER);
|
|
105
|
+
if (!legacy) {
|
|
106
|
+
if (!found) {
|
|
107
|
+
return {
|
|
108
|
+
write: false,
|
|
109
|
+
note: `${path} already exists and was not generated by nola — left untouched`,
|
|
110
|
+
stale: false,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (found[1] === version)
|
|
114
|
+
return { write: false, note: `${path} is up to date (v${version})`, stale: false };
|
|
115
|
+
}
|
|
116
|
+
if (force)
|
|
117
|
+
return { write: true };
|
|
118
|
+
const what = legacy
|
|
119
|
+
? `${path} uses the superseded pointer form`
|
|
120
|
+
: `${path} is stale (v${found?.[1]} → v${version})`;
|
|
121
|
+
return { write: false, note: `${what} — re-run with --force to replace it`, stale: true };
|
|
122
|
+
}
|
|
123
|
+
/** Copy the canonical skill dir verbatim (stamp aside) into .claude/skills/nola/. */
|
|
124
|
+
async function writeClaudeSkill(root, s, version) {
|
|
125
|
+
const dir = join(root, ".claude", "skills", "nola");
|
|
126
|
+
await mkdir(join(dir, "references"), { recursive: true });
|
|
127
|
+
const stamped = s.full.replace(/^(---\n[\s\S]*?\n---\n)/, `$1${stamp(version)}\n`);
|
|
128
|
+
await writeFile(join(dir, "SKILL.md"), stamped);
|
|
129
|
+
const wrote = [".claude/skills/nola/SKILL.md"];
|
|
130
|
+
for (const name of (await readdir(join(SKILLS_DIR, "references"))).sort()) {
|
|
131
|
+
await writeFile(join(dir, "references", name), await readFile(join(SKILLS_DIR, "references", name), "utf8"));
|
|
132
|
+
wrote.push(`.claude/skills/nola/references/${name}`);
|
|
133
|
+
}
|
|
134
|
+
return wrote;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Write the selected adapters as self-contained, version-stamped content.
|
|
138
|
+
* Existing files are classified, never blindly overwritten (spec §3).
|
|
139
|
+
*/
|
|
140
|
+
export async function writeAgentSkills(targetDir, agents, opts = {}) {
|
|
100
141
|
const root = resolve(targetDir);
|
|
142
|
+
const version = await ownVersion();
|
|
143
|
+
const source = await readSkillSource();
|
|
144
|
+
const force = opts.force === true;
|
|
101
145
|
const wrote = [];
|
|
102
146
|
const skipped = [];
|
|
147
|
+
let stale = false;
|
|
148
|
+
const flat = {
|
|
149
|
+
cursor: { path: ".cursor/rules/nola.mdc", content: cursorRule(source, version) },
|
|
150
|
+
copilot: { path: ".github/instructions/nola.instructions.md", content: copilotInstructions(source, version) },
|
|
151
|
+
"agents-md": { path: "AGENTS.md", content: agentsMd(source, version) },
|
|
152
|
+
};
|
|
103
153
|
for (const id of AGENT_IDS) {
|
|
104
154
|
if (!agents.includes(id))
|
|
105
155
|
continue;
|
|
106
|
-
|
|
156
|
+
if (id === "claude") {
|
|
157
|
+
const marker = join(root, ".claude", "skills", "nola", "SKILL.md");
|
|
158
|
+
if (existsSync(marker)) {
|
|
159
|
+
const d = disposition(".claude/skills/nola/SKILL.md", await readFile(marker, "utf8"), version, force);
|
|
160
|
+
if (!d.write) {
|
|
161
|
+
skipped.push(d.note);
|
|
162
|
+
stale ||= d.stale;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
wrote.push(...(await writeClaudeSkill(root, source, version)));
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const { path, content } = flat[id];
|
|
107
170
|
const abs = join(root, ...path.split("/"));
|
|
108
171
|
if (existsSync(abs)) {
|
|
109
|
-
// AGENTS.md
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
172
|
+
// AGENTS.md is a user file: never modified, --force included.
|
|
173
|
+
if (id === "agents-md") {
|
|
174
|
+
skipped.push(`AGENTS.md already exists — left untouched. Add this section manually:\n\n${agentsSection(source, version)}`);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const d = disposition(path, await readFile(abs, "utf8"), version, force);
|
|
178
|
+
if (!d.write) {
|
|
179
|
+
skipped.push(d.note);
|
|
180
|
+
stale ||= d.stale;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
114
183
|
}
|
|
115
184
|
await mkdir(dirname(abs), { recursive: true });
|
|
116
185
|
await writeFile(abs, content);
|
|
117
186
|
wrote.push(path);
|
|
118
187
|
}
|
|
119
|
-
return { wrote, skipped };
|
|
188
|
+
return { wrote, skipped, stale };
|
|
120
189
|
}
|
|
121
190
|
//# sourceMappingURL=agents.js.map
|
package/dist/agents.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"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"}
|
|
@@ -224,61 +224,60 @@ async function addNola(targetDir, opts = {}) {
|
|
|
224
224
|
|
|
225
225
|
// src/agents.ts
|
|
226
226
|
import { existsSync as existsSync4 } from "node:fs";
|
|
227
|
-
import { mkdir as mkdir2, writeFile as writeFile3 } from "node:fs/promises";
|
|
227
|
+
import { mkdir as mkdir2, readdir as readdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
228
228
|
import { dirname, join as join4, resolve as resolve3 } from "node:path";
|
|
229
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
229
230
|
var AGENT_IDS = ["claude", "cursor", "copilot", "agents-md"];
|
|
230
231
|
var AGENT_OPTIONS = [
|
|
231
|
-
{ value: "agents-md", label: "AGENTS.md", hint: "cross-agent
|
|
232
|
-
{ value: "claude", label: "Claude Code", hint: ".claude/skills/nola/
|
|
232
|
+
{ value: "agents-md", label: "AGENTS.md", hint: "cross-agent instructions (Codex, Gemini CLI, \u2026)" },
|
|
233
|
+
{ value: "claude", label: "Claude Code", hint: ".claude/skills/nola/ (full skill + references)" },
|
|
233
234
|
{ value: "cursor", label: "Cursor", hint: ".cursor/rules/nola.mdc" },
|
|
234
235
|
{ value: "copilot", label: "GitHub Copilot", hint: ".github/instructions/nola.instructions.md" }
|
|
235
236
|
];
|
|
236
|
-
var
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
237
|
+
var SKILLS_DIR = fileURLToPath3(new URL("../skills/nola/", import.meta.url));
|
|
238
|
+
var STAMP_RE = /^<!-- nola-skill v(\S+)/m;
|
|
239
|
+
var stamp = (version) => `<!-- nola-skill v${version} \u2014 regenerate with: nola skill install --force -->`;
|
|
240
|
+
var LEGACY_MARKER = "node_modules/nola-lang/skills/nola";
|
|
241
|
+
async function readSkillSource() {
|
|
242
|
+
const full = (await readFile4(join4(SKILLS_DIR, "SKILL.md"), "utf8")).replaceAll("\r\n", "\n");
|
|
243
|
+
const fm = /^---\n([\s\S]*?)\n---\n/.exec(full);
|
|
244
|
+
if (!fm) throw new Error("SKILL.md is missing its frontmatter block");
|
|
245
|
+
const afterFm = full.slice(fm[0].length);
|
|
246
|
+
const refsAt = afterFm.indexOf("\n## References");
|
|
247
|
+
const body = (refsAt === -1 ? afterFm : afterFm.slice(0, refsAt)).trim();
|
|
248
|
+
return { frontmatter: fm[1], body, full };
|
|
249
|
+
}
|
|
250
|
+
var LOCAL_REFERENCES = [
|
|
251
|
+
"Deeper references, when this repo has them:",
|
|
252
|
+
"`.claude/skills/nola/references/` \u2014 syntax.md, patterns.md, config.md,",
|
|
253
|
+
"pitfalls.md."
|
|
254
|
+
].join("\n");
|
|
255
|
+
function cursorRule(s, version) {
|
|
256
|
+
return [
|
|
257
|
+
"---",
|
|
258
|
+
"description: Nola (.tsi) language rules",
|
|
259
|
+
'globs: ["**/*.tsi", "nola.config.ts"]',
|
|
260
|
+
"alwaysApply: false",
|
|
261
|
+
"---",
|
|
262
|
+
stamp(version),
|
|
263
|
+
"",
|
|
264
|
+
s.body,
|
|
265
|
+
"",
|
|
266
|
+
LOCAL_REFERENCES,
|
|
267
|
+
""
|
|
268
|
+
].join("\n");
|
|
269
|
+
}
|
|
270
|
+
function copilotInstructions(s, version) {
|
|
271
|
+
return ["---", 'applyTo: "**/*.tsi"', "---", stamp(version), "", s.body, "", LOCAL_REFERENCES, ""].join("\n");
|
|
272
|
+
}
|
|
273
|
+
function agentsSection(s, version) {
|
|
274
|
+
return ["## Nola", "", stamp(version), "", s.body, "", LOCAL_REFERENCES, ""].join("\n");
|
|
275
|
+
}
|
|
276
|
+
function agentsMd(s, version) {
|
|
277
|
+
return `# AGENTS.md
|
|
274
278
|
|
|
275
|
-
${
|
|
276
|
-
|
|
277
|
-
claude: { path: ".claude/skills/nola/SKILL.md", content: CLAUDE_SKILL },
|
|
278
|
-
cursor: { path: ".cursor/rules/nola.mdc", content: CURSOR_RULE },
|
|
279
|
-
copilot: { path: ".github/instructions/nola.instructions.md", content: COPILOT_INSTRUCTIONS },
|
|
280
|
-
"agents-md": { path: "AGENTS.md", content: AGENTS_MD }
|
|
281
|
-
};
|
|
279
|
+
${agentsSection(s, version)}`;
|
|
280
|
+
}
|
|
282
281
|
function detectAgents(targetDir) {
|
|
283
282
|
const root = resolve3(targetDir);
|
|
284
283
|
const found = [];
|
|
@@ -301,27 +300,87 @@ function parseAgentsFlag(value) {
|
|
|
301
300
|
}
|
|
302
301
|
return [...new Set(ids)];
|
|
303
302
|
}
|
|
304
|
-
|
|
303
|
+
function disposition(path, existing, version, force) {
|
|
304
|
+
const found = STAMP_RE.exec(existing);
|
|
305
|
+
const legacy = existing.includes(LEGACY_MARKER);
|
|
306
|
+
if (!legacy) {
|
|
307
|
+
if (!found) {
|
|
308
|
+
return {
|
|
309
|
+
write: false,
|
|
310
|
+
note: `${path} already exists and was not generated by nola \u2014 left untouched`,
|
|
311
|
+
stale: false
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
if (found[1] === version) return { write: false, note: `${path} is up to date (v${version})`, stale: false };
|
|
315
|
+
}
|
|
316
|
+
if (force) return { write: true };
|
|
317
|
+
const what = legacy ? `${path} uses the superseded pointer form` : `${path} is stale (v${found?.[1]} \u2192 v${version})`;
|
|
318
|
+
return { write: false, note: `${what} \u2014 re-run with --force to replace it`, stale: true };
|
|
319
|
+
}
|
|
320
|
+
async function writeClaudeSkill(root, s, version) {
|
|
321
|
+
const dir = join4(root, ".claude", "skills", "nola");
|
|
322
|
+
await mkdir2(join4(dir, "references"), { recursive: true });
|
|
323
|
+
const stamped = s.full.replace(/^(---\n[\s\S]*?\n---\n)/, `$1${stamp(version)}
|
|
324
|
+
`);
|
|
325
|
+
await writeFile3(join4(dir, "SKILL.md"), stamped);
|
|
326
|
+
const wrote = [".claude/skills/nola/SKILL.md"];
|
|
327
|
+
for (const name of (await readdir3(join4(SKILLS_DIR, "references"))).sort()) {
|
|
328
|
+
await writeFile3(join4(dir, "references", name), await readFile4(join4(SKILLS_DIR, "references", name), "utf8"));
|
|
329
|
+
wrote.push(`.claude/skills/nola/references/${name}`);
|
|
330
|
+
}
|
|
331
|
+
return wrote;
|
|
332
|
+
}
|
|
333
|
+
async function writeAgentSkills(targetDir, agents, opts = {}) {
|
|
305
334
|
const root = resolve3(targetDir);
|
|
335
|
+
const version = await ownVersion();
|
|
336
|
+
const source = await readSkillSource();
|
|
337
|
+
const force = opts.force === true;
|
|
306
338
|
const wrote = [];
|
|
307
339
|
const skipped = [];
|
|
340
|
+
let stale = false;
|
|
341
|
+
const flat = {
|
|
342
|
+
cursor: { path: ".cursor/rules/nola.mdc", content: cursorRule(source, version) },
|
|
343
|
+
copilot: { path: ".github/instructions/nola.instructions.md", content: copilotInstructions(source, version) },
|
|
344
|
+
"agents-md": { path: "AGENTS.md", content: agentsMd(source, version) }
|
|
345
|
+
};
|
|
308
346
|
for (const id of AGENT_IDS) {
|
|
309
347
|
if (!agents.includes(id)) continue;
|
|
310
|
-
|
|
348
|
+
if (id === "claude") {
|
|
349
|
+
const marker = join4(root, ".claude", "skills", "nola", "SKILL.md");
|
|
350
|
+
if (existsSync4(marker)) {
|
|
351
|
+
const d = disposition(".claude/skills/nola/SKILL.md", await readFile4(marker, "utf8"), version, force);
|
|
352
|
+
if (!d.write) {
|
|
353
|
+
skipped.push(d.note);
|
|
354
|
+
stale ||= d.stale;
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
wrote.push(...await writeClaudeSkill(root, source, version));
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
const { path, content } = flat[id];
|
|
311
362
|
const abs = join4(root, ...path.split("/"));
|
|
312
363
|
if (existsSync4(abs)) {
|
|
313
|
-
|
|
314
|
-
|
|
364
|
+
if (id === "agents-md") {
|
|
365
|
+
skipped.push(
|
|
366
|
+
`AGENTS.md already exists \u2014 left untouched. Add this section manually:
|
|
315
367
|
|
|
316
|
-
${
|
|
317
|
-
|
|
318
|
-
|
|
368
|
+
${agentsSection(source, version)}`
|
|
369
|
+
);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
const d = disposition(path, await readFile4(abs, "utf8"), version, force);
|
|
373
|
+
if (!d.write) {
|
|
374
|
+
skipped.push(d.note);
|
|
375
|
+
stale ||= d.stale;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
319
378
|
}
|
|
320
379
|
await mkdir2(dirname(abs), { recursive: true });
|
|
321
380
|
await writeFile3(abs, content);
|
|
322
381
|
wrote.push(path);
|
|
323
382
|
}
|
|
324
|
-
return { wrote, skipped };
|
|
383
|
+
return { wrote, skipped, stale };
|
|
325
384
|
}
|
|
326
385
|
|
|
327
386
|
// src/ide.ts
|
|
@@ -375,8 +434,19 @@ async function writeVscodeSetup(targetDir) {
|
|
|
375
434
|
|
|
376
435
|
// src/flow.ts
|
|
377
436
|
import { existsSync as existsSync6 } from "node:fs";
|
|
378
|
-
import { readdir as
|
|
437
|
+
import { readdir as readdir4, readFile as readFile5 } from "node:fs/promises";
|
|
379
438
|
import { basename as basename2, join as join6, resolve as resolve5 } from "node:path";
|
|
439
|
+
|
|
440
|
+
// src/package-manager.ts
|
|
441
|
+
function detectPackageManager(userAgent = process.env.npm_config_user_agent) {
|
|
442
|
+
const name = userAgent?.split("/")[0]?.trim();
|
|
443
|
+
return name === "pnpm" || name === "yarn" || name === "bun" ? name : "npm";
|
|
444
|
+
}
|
|
445
|
+
function packageManagerCommands(pm) {
|
|
446
|
+
return { install: `${pm} install`, start: `${pm} start`, run: `${pm} run` };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/flow.ts
|
|
380
450
|
var DEFAULT_DIR = "nola-app";
|
|
381
451
|
var DEFAULT_TEMPLATE = "starter";
|
|
382
452
|
function templateOption(t) {
|
|
@@ -427,7 +497,7 @@ async function resolveScaffoldOptions(input, prompter) {
|
|
|
427
497
|
if (existsSync6(cwdManifest)) {
|
|
428
498
|
let pkgName = "this project";
|
|
429
499
|
try {
|
|
430
|
-
const parsed = JSON.parse(await
|
|
500
|
+
const parsed = JSON.parse(await readFile5(cwdManifest, "utf8"));
|
|
431
501
|
if (parsed.name) pkgName = `"${parsed.name}"`;
|
|
432
502
|
} catch {
|
|
433
503
|
}
|
|
@@ -453,7 +523,7 @@ async function resolveScaffoldOptions(input, prompter) {
|
|
|
453
523
|
}
|
|
454
524
|
let force = false;
|
|
455
525
|
const absRoot = resolve5(dir);
|
|
456
|
-
if (existsSync6(absRoot) && (await
|
|
526
|
+
if (existsSync6(absRoot) && (await readdir4(absRoot)).length > 0) {
|
|
457
527
|
if (!input.interactive) throw new Error(`target directory ${absRoot} is not empty`);
|
|
458
528
|
if (existsSync6(join6(absRoot, "package.json"))) {
|
|
459
529
|
const choice = await prompter.select(`Target directory "${dir}" is not empty and has a package.json.`, [
|
|
@@ -511,9 +581,11 @@ function plainPrompter() {
|
|
|
511
581
|
outro: (m) => console.log(m)
|
|
512
582
|
};
|
|
513
583
|
}
|
|
514
|
-
function nextSteps(outcome, fileCount, name) {
|
|
515
|
-
const
|
|
516
|
-
const
|
|
584
|
+
function nextSteps(outcome, fileCount, name, pm) {
|
|
585
|
+
const cmd = packageManagerCommands(pm);
|
|
586
|
+
const start = cmd.start.padEnd(16);
|
|
587
|
+
const keyless = outcome.template === "empty" ? `${start} # set OPENAI_API_KEY first` : `${start} # runs offline \u2014 no API key needed`;
|
|
588
|
+
const lines = outcome.dir === "." ? [cmd.install, keyless] : [`cd ${outcome.dir}`, cmd.install, keyless];
|
|
517
589
|
return `Scaffolded ${name} (${fileCount} files).
|
|
518
590
|
|
|
519
591
|
Next steps:
|
|
@@ -531,6 +603,7 @@ async function runFlow(args, opts = {}) {
|
|
|
531
603
|
}
|
|
532
604
|
}
|
|
533
605
|
if (interactive) prompter.intro?.(opts.intro ?? `create-nola-lang v${await ownVersion()}`);
|
|
606
|
+
const pm = opts.packageManager ?? detectPackageManager();
|
|
534
607
|
const input = {
|
|
535
608
|
dir: args.dir,
|
|
536
609
|
template: args.template,
|
|
@@ -557,7 +630,7 @@ async function runFlow(args, opts = {}) {
|
|
|
557
630
|
return 0;
|
|
558
631
|
}
|
|
559
632
|
const lines = [
|
|
560
|
-
|
|
633
|
+
packageManagerCommands(pm).install,
|
|
561
634
|
'optional start script: "start": "nola run src/main.ts"',
|
|
562
635
|
'tsconfig tip: directory-style include (e.g. ["src"]) lets the editor see .tsi files'
|
|
563
636
|
];
|
|
@@ -586,7 +659,8 @@ Next steps:
|
|
|
586
659
|
const message = nextSteps(
|
|
587
660
|
outcome,
|
|
588
661
|
files.length + ideWrote.length + agWrote.length,
|
|
589
|
-
basename2(resolve5(outcome.dir))
|
|
662
|
+
basename2(resolve5(outcome.dir)),
|
|
663
|
+
pm
|
|
590
664
|
);
|
|
591
665
|
if (prompter.outro) prompter.outro(message);
|
|
592
666
|
else console.log(message);
|
package/dist/flow.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AgentId } from "./agents.js";
|
|
2
|
+
import { type PackageManager } from "./package-manager.js";
|
|
2
3
|
export interface PrompterOption {
|
|
3
4
|
value: string;
|
|
4
5
|
label: string;
|
|
@@ -58,6 +59,8 @@ export interface RunFlowOptions {
|
|
|
58
59
|
interactive?: boolean;
|
|
59
60
|
/** bare-run detection root; default "." — tests pass a tmp dir */
|
|
60
61
|
cwd?: string;
|
|
62
|
+
/** the manager that invoked us; default: detected from npm_config_user_agent */
|
|
63
|
+
packageManager?: PackageManager;
|
|
61
64
|
}
|
|
62
65
|
/** The shared entry for both `npm create nola-lang` and `nola init`. */
|
|
63
66
|
export declare function runFlow(args: RunFlowArgs, opts?: RunFlowOptions): Promise<number>;
|