create-nola-lang 0.1.6 → 0.1.8
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/dist/agents.d.ts +16 -10
- package/dist/agents.js +72 -80
- package/dist/{chunk-34XSPT6F.js → chunk-GUYF4NM5.js} +58 -77
- package/dist/flow.js +4 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -4
- package/dist/main.js +1 -1
- package/dist/skill-install.js +4 -2
- package/package.json +2 -1
- package/skills/nola/SKILL.md +19 -3
- package/skills/nola/references/config.md +3 -3
- package/skills/nola/references/patterns.md +13 -1
- package/skills/nola/references/pitfalls.md +12 -10
- package/skills/nola/references/syntax.md +80 -7
- package/dist/add.d.ts.map +0 -1
- package/dist/add.js.map +0 -1
- package/dist/agents.d.ts.map +0 -1
- package/dist/agents.js.map +0 -1
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js.map +0 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/checkout.d.ts.map +0 -1
- package/dist/checkout.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/credentials.d.ts.map +0 -1
- package/dist/credentials.js.map +0 -1
- package/dist/examples.d.ts.map +0 -1
- package/dist/examples.js.map +0 -1
- package/dist/flow.d.ts.map +0 -1
- package/dist/flow.js.map +0 -1
- package/dist/github.d.ts.map +0 -1
- package/dist/github.js.map +0 -1
- package/dist/home-config.d.ts.map +0 -1
- package/dist/home-config.js.map +0 -1
- package/dist/ide.d.ts.map +0 -1
- package/dist/ide.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/key.d.ts.map +0 -1
- package/dist/key.js.map +0 -1
- package/dist/launch.d.ts.map +0 -1
- package/dist/launch.js.map +0 -1
- package/dist/main.d.ts.map +0 -1
- package/dist/main.js.map +0 -1
- package/dist/node-version.d.ts.map +0 -1
- package/dist/node-version.js.map +0 -1
- package/dist/open-url.d.ts.map +0 -1
- package/dist/open-url.js.map +0 -1
- package/dist/package-manager.d.ts.map +0 -1
- package/dist/package-manager.js.map +0 -1
- package/dist/palette.d.ts.map +0 -1
- package/dist/palette.js.map +0 -1
- package/dist/prompter.d.ts.map +0 -1
- package/dist/prompter.js.map +0 -1
- package/dist/providers.d.ts.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js.map +0 -1
- package/dist/scaffold.d.ts.map +0 -1
- package/dist/scaffold.js.map +0 -1
- package/dist/skill-install.d.ts.map +0 -1
- package/dist/skill-install.js.map +0 -1
- package/dist/style.d.ts.map +0 -1
- package/dist/style.js.map +0 -1
- package/dist/trial.d.ts.map +0 -1
- package/dist/trial.js.map +0 -1
package/dist/agents.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
export type AgentId = "claude" | "
|
|
1
|
+
export type AgentId = "claude" | "universal" | "agents-md";
|
|
2
2
|
export declare const AGENT_IDS: readonly AgentId[];
|
|
3
3
|
/** `nola skill install`'s agents multiselect (the scaffold asks its own gated list, see flow.ts). */
|
|
4
4
|
export declare const SKILL_AGENTS_QUESTION = "Set up coding agents? (Space toggles, Enter confirms)";
|
|
5
|
+
/**
|
|
6
|
+
* Where each skill target writes the canonical directory (project-relative
|
|
7
|
+
* posix). Claude Code reads only `.claude/skills/`; `.agents/skills/` is the
|
|
8
|
+
* open Agent Skills location — "universal" in the skills CLI's vocabulary —
|
|
9
|
+
* that Cursor, Copilot, Codex, Gemini CLI and most other agents read.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SKILL_DIRS: Readonly<Record<Exclude<AgentId, "agents-md">, string>>;
|
|
5
12
|
/** Shared option list for every agents multiselect. */
|
|
6
13
|
export declare const AGENT_OPTIONS: {
|
|
7
14
|
value: AgentId;
|
|
@@ -9,30 +16,29 @@ export declare const AGENT_OPTIONS: {
|
|
|
9
16
|
hint: string;
|
|
10
17
|
}[];
|
|
11
18
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* never drift from each other (spec §2).
|
|
19
|
+
* The AGENTS.md section embeds SKILL.md's BODY verbatim, so it can never
|
|
20
|
+
* drift from the skill directory (spec §2).
|
|
15
21
|
*/
|
|
16
22
|
export interface SkillSource {
|
|
17
23
|
/** the frontmatter block of SKILL.md, without the --- fences */
|
|
18
24
|
frontmatter: string;
|
|
19
25
|
/** the first heading up to (not including) `## References` */
|
|
20
26
|
body: string;
|
|
21
|
-
/** the whole file, verbatim — what the
|
|
27
|
+
/** the whole file, verbatim — what the skill directories copy */
|
|
22
28
|
full: string;
|
|
23
29
|
}
|
|
24
30
|
export declare function readSkillSource(): Promise<SkillSource>;
|
|
25
31
|
/** The `## Nola` section for AGENTS.md — also the paste snippet when skipped. */
|
|
26
32
|
export declare function agentsSection(s: SkillSource, version: string): string;
|
|
27
|
-
/**
|
|
28
|
-
export declare function
|
|
29
|
-
/** The interactive preselection: detected agents + Claude Code (the wizard's default agent — deduped when detected). */
|
|
30
|
-
export declare function defaultAgents(targetDir: string): AgentId[];
|
|
33
|
+
/** The interactive preselection: both skill copies, so a plain Enter covers every skills-aware agent. */
|
|
34
|
+
export declare function defaultAgents(_targetDir: string): AgentId[];
|
|
31
35
|
/** "all" | "none" | comma list of ids; unknown ids throw listing valid values. */
|
|
32
36
|
export declare function parseAgentsFlag(value: string): AgentId[];
|
|
33
37
|
export interface AgentSetupResult {
|
|
34
38
|
/** project-relative posix paths written */
|
|
35
39
|
wrote: string[];
|
|
40
|
+
/** superseded stamped adapters deleted under --force (project-relative posix paths) */
|
|
41
|
+
removed: string[];
|
|
36
42
|
/** human-readable notes about files left untouched */
|
|
37
43
|
skipped: string[];
|
|
38
44
|
/** true when something was left stale — the caller can suggest --force */
|
|
@@ -43,7 +49,7 @@ export interface WriteAgentSkillsOptions {
|
|
|
43
49
|
force?: boolean;
|
|
44
50
|
}
|
|
45
51
|
/**
|
|
46
|
-
* Write the selected
|
|
52
|
+
* Write the selected targets as self-contained, version-stamped content.
|
|
47
53
|
* Existing files are classified, never blindly overwritten (spec §3).
|
|
48
54
|
*/
|
|
49
55
|
export declare function writeAgentSkills(targetDir: string, agents: AgentId[], opts?: WriteAgentSkillsOptions): Promise<AgentSetupResult>;
|
package/dist/agents.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { ownVersion } from "./scaffold.js";
|
|
6
|
-
export const AGENT_IDS = ["claude", "
|
|
6
|
+
export const AGENT_IDS = ["claude", "universal", "agents-md"];
|
|
7
7
|
/** `nola skill install`'s agents multiselect (the scaffold asks its own gated list, see flow.ts). */
|
|
8
8
|
export const SKILL_AGENTS_QUESTION = "Set up coding agents? (Space toggles, Enter confirms)";
|
|
9
|
+
/**
|
|
10
|
+
* Where each skill target writes the canonical directory (project-relative
|
|
11
|
+
* posix). Claude Code reads only `.claude/skills/`; `.agents/skills/` is the
|
|
12
|
+
* open Agent Skills location — "universal" in the skills CLI's vocabulary —
|
|
13
|
+
* that Cursor, Copilot, Codex, Gemini CLI and most other agents read.
|
|
14
|
+
*/
|
|
15
|
+
export const SKILL_DIRS = {
|
|
16
|
+
claude: ".claude/skills/nola",
|
|
17
|
+
universal: ".agents/skills/nola",
|
|
18
|
+
};
|
|
9
19
|
/** Shared option list for every agents multiselect. */
|
|
10
20
|
export const AGENT_OPTIONS = [
|
|
11
|
-
{ value: "
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
21
|
+
{ value: "claude", label: "Claude Code", hint: ".claude/skills/nola/ (the only directory Claude Code reads)" },
|
|
22
|
+
{
|
|
23
|
+
value: "universal",
|
|
24
|
+
label: "Cursor, Copilot, Codex, Gemini CLI, …",
|
|
25
|
+
hint: ".agents/skills/nola/ (the open Agent Skills location)",
|
|
26
|
+
},
|
|
27
|
+
{ value: "agents-md", label: "AGENTS.md", hint: "the skill body inline, for agents that read only AGENTS.md" },
|
|
15
28
|
];
|
|
16
29
|
/**
|
|
17
30
|
* The canonical skill content, resolved relative to this module — `src/` in
|
|
@@ -22,6 +35,11 @@ const SKILLS_DIR = fileURLToPath(new URL("../skills/nola/", import.meta.url));
|
|
|
22
35
|
/** Marks a generated file so staleness is detectable (spec §3). */
|
|
23
36
|
const STAMP_RE = /^<!-- nola-skill v(\S+)/m;
|
|
24
37
|
const stamp = (version) => `<!-- nola-skill v${version} — regenerate with: nola skill install --force -->`;
|
|
38
|
+
/**
|
|
39
|
+
* Adapters written by 0.1.0–0.1.7 that the `.agents/skills` layout replaced
|
|
40
|
+
* (spec revision 2026-09-15). Stamped ones are ours to remove under --force.
|
|
41
|
+
*/
|
|
42
|
+
const LEGACY_ADAPTERS = [".cursor/rules/nola.mdc", ".github/instructions/nola.instructions.md"];
|
|
25
43
|
export async function readSkillSource() {
|
|
26
44
|
const full = (await readFile(join(SKILLS_DIR, "SKILL.md"), "utf8")).replaceAll("\r\n", "\n");
|
|
27
45
|
const fm = /^---\n([\s\S]*?)\n---\n/.exec(full);
|
|
@@ -32,30 +50,12 @@ export async function readSkillSource() {
|
|
|
32
50
|
const body = (refsAt === -1 ? afterFm : afterFm.slice(0, refsAt)).trim();
|
|
33
51
|
return { frontmatter: fm[1], body, full };
|
|
34
52
|
}
|
|
35
|
-
/** Points at the project-local
|
|
53
|
+
/** Points at the project-local copies — never node_modules (spec §1). */
|
|
36
54
|
const LOCAL_REFERENCES = [
|
|
37
55
|
"Deeper references, when this repo has them:",
|
|
38
|
-
"`.claude/skills/nola/references/` —
|
|
39
|
-
"pitfalls.md.",
|
|
56
|
+
"`.agents/skills/nola/references/` or `.claude/skills/nola/references/` —",
|
|
57
|
+
"syntax.md, patterns.md, config.md, pitfalls.md.",
|
|
40
58
|
].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
59
|
/** The `## Nola` section for AGENTS.md — also the paste snippet when skipped. */
|
|
60
60
|
export function agentsSection(s, version) {
|
|
61
61
|
return ["## Nola", "", stamp(version), "", s.body, "", LOCAL_REFERENCES, ""].join("\n");
|
|
@@ -63,21 +63,9 @@ export function agentsSection(s, version) {
|
|
|
63
63
|
function agentsMd(s, version) {
|
|
64
64
|
return `# AGENTS.md\n\n${agentsSection(s, version)}`;
|
|
65
65
|
}
|
|
66
|
-
/**
|
|
67
|
-
export function
|
|
68
|
-
|
|
69
|
-
const found = [];
|
|
70
|
-
if (existsSync(join(root, ".claude")) || existsSync(join(root, "CLAUDE.md")))
|
|
71
|
-
found.push("claude");
|
|
72
|
-
if (existsSync(join(root, ".cursor")))
|
|
73
|
-
found.push("cursor");
|
|
74
|
-
if (existsSync(join(root, ".github")))
|
|
75
|
-
found.push("copilot");
|
|
76
|
-
return found;
|
|
77
|
-
}
|
|
78
|
-
/** The interactive preselection: detected agents + Claude Code (the wizard's default agent — deduped when detected). */
|
|
79
|
-
export function defaultAgents(targetDir) {
|
|
80
|
-
return [...new Set([...detectAgents(targetDir), "claude"])];
|
|
66
|
+
/** The interactive preselection: both skill copies, so a plain Enter covers every skills-aware agent. */
|
|
67
|
+
export function defaultAgents(_targetDir) {
|
|
68
|
+
return ["claude", "universal"];
|
|
81
69
|
}
|
|
82
70
|
/** "all" | "none" | comma list of ids; unknown ids throw listing valid values. */
|
|
83
71
|
export function parseAgentsFlag(value) {
|
|
@@ -116,21 +104,21 @@ function disposition(path, existing, version, force) {
|
|
|
116
104
|
stale: true,
|
|
117
105
|
};
|
|
118
106
|
}
|
|
119
|
-
/** Copy the canonical skill dir verbatim (stamp aside) into
|
|
120
|
-
async function
|
|
121
|
-
const dir = join(root,
|
|
107
|
+
/** Copy the canonical skill dir verbatim (stamp aside) into `<root>/<base>/`. */
|
|
108
|
+
async function writeSkillDir(root, base, s, version) {
|
|
109
|
+
const dir = join(root, ...base.split("/"));
|
|
122
110
|
await mkdir(join(dir, "references"), { recursive: true });
|
|
123
111
|
const stamped = s.full.replace(/^(---\n[\s\S]*?\n---\n)/, `$1${stamp(version)}\n`);
|
|
124
112
|
await writeFile(join(dir, "SKILL.md"), stamped);
|
|
125
|
-
const wrote = [
|
|
113
|
+
const wrote = [`${base}/SKILL.md`];
|
|
126
114
|
for (const name of (await readdir(join(SKILLS_DIR, "references"))).sort()) {
|
|
127
115
|
await writeFile(join(dir, "references", name), await readFile(join(SKILLS_DIR, "references", name), "utf8"));
|
|
128
|
-
wrote.push(
|
|
116
|
+
wrote.push(`${base}/references/${name}`);
|
|
129
117
|
}
|
|
130
118
|
return wrote;
|
|
131
119
|
}
|
|
132
120
|
/**
|
|
133
|
-
* Write the selected
|
|
121
|
+
* Write the selected targets as self-contained, version-stamped content.
|
|
134
122
|
* Existing files are classified, never blindly overwritten (spec §3).
|
|
135
123
|
*/
|
|
136
124
|
export async function writeAgentSkills(targetDir, agents, opts = {}) {
|
|
@@ -139,48 +127,52 @@ export async function writeAgentSkills(targetDir, agents, opts = {}) {
|
|
|
139
127
|
const source = await readSkillSource();
|
|
140
128
|
const force = opts.force === true;
|
|
141
129
|
const wrote = [];
|
|
130
|
+
const removed = [];
|
|
142
131
|
const skipped = [];
|
|
143
132
|
let stale = false;
|
|
144
|
-
const
|
|
145
|
-
cursor: { path: ".cursor/rules/nola.mdc", content: cursorRule(source, version) },
|
|
146
|
-
copilot: { path: ".github/instructions/nola.instructions.md", content: copilotInstructions(source, version) },
|
|
147
|
-
"agents-md": { path: "AGENTS.md", content: agentsMd(source, version) },
|
|
148
|
-
};
|
|
149
|
-
for (const id of AGENT_IDS) {
|
|
133
|
+
for (const id of ["claude", "universal"]) {
|
|
150
134
|
if (!agents.includes(id))
|
|
151
135
|
continue;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (!d.write) {
|
|
157
|
-
skipped.push(d.note);
|
|
158
|
-
stale ||= d.stale;
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
wrote.push(...(await writeClaudeSkill(root, source, version)));
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
const { path, content } = flat[id];
|
|
166
|
-
const abs = join(root, ...path.split("/"));
|
|
167
|
-
if (existsSync(abs)) {
|
|
168
|
-
// AGENTS.md is a user file: never modified, --force included.
|
|
169
|
-
if (id === "agents-md") {
|
|
170
|
-
skipped.push(`AGENTS.md already exists — left untouched. Add this section manually:\n\n${agentsSection(source, version)}`);
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
const d = disposition(path, await readFile(abs, "utf8"), version, force);
|
|
136
|
+
const base = SKILL_DIRS[id];
|
|
137
|
+
const marker = join(root, ...base.split("/"), "SKILL.md");
|
|
138
|
+
if (existsSync(marker)) {
|
|
139
|
+
const d = disposition(`${base}/SKILL.md`, await readFile(marker, "utf8"), version, force);
|
|
174
140
|
if (!d.write) {
|
|
175
141
|
skipped.push(d.note);
|
|
176
142
|
stale ||= d.stale;
|
|
177
143
|
continue;
|
|
178
144
|
}
|
|
179
145
|
}
|
|
180
|
-
await
|
|
181
|
-
|
|
182
|
-
|
|
146
|
+
wrote.push(...(await writeSkillDir(root, base, source, version)));
|
|
147
|
+
}
|
|
148
|
+
if (agents.includes("universal")) {
|
|
149
|
+
// Stamped legacy adapters are ours: superseded by .agents/skills, removed under --force.
|
|
150
|
+
for (const path of LEGACY_ADAPTERS) {
|
|
151
|
+
const abs = join(root, ...path.split("/"));
|
|
152
|
+
if (!existsSync(abs) || !STAMP_RE.test(await readFile(abs, "utf8")))
|
|
153
|
+
continue;
|
|
154
|
+
if (force) {
|
|
155
|
+
await rm(abs);
|
|
156
|
+
removed.push(path);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
skipped.push(`${path} is superseded by .agents/skills/nola — re-run with --force to remove it`);
|
|
160
|
+
stale = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (agents.includes("agents-md")) {
|
|
165
|
+
const abs = join(root, "AGENTS.md");
|
|
166
|
+
if (existsSync(abs)) {
|
|
167
|
+
// AGENTS.md is a user file: never modified, --force included.
|
|
168
|
+
skipped.push(`AGENTS.md already exists — left untouched. Add this section manually:\n\n${agentsSection(source, version)}`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
172
|
+
await writeFile(abs, agentsMd(source, version));
|
|
173
|
+
wrote.push("AGENTS.md");
|
|
174
|
+
}
|
|
183
175
|
}
|
|
184
|
-
return { wrote, skipped, stale };
|
|
176
|
+
return { wrote, removed, skipped, stale };
|
|
185
177
|
}
|
|
186
178
|
//# sourceMappingURL=agents.js.map
|
|
@@ -345,20 +345,28 @@ async function addNola(targetDir, opts = {}) {
|
|
|
345
345
|
|
|
346
346
|
// src/agents.ts
|
|
347
347
|
import { existsSync as existsSync5 } from "node:fs";
|
|
348
|
-
import { mkdir as mkdir3, readdir as readdir3, readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
|
|
348
|
+
import { mkdir as mkdir3, readdir as readdir3, readFile as readFile5, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
|
|
349
349
|
import { dirname as dirname2, join as join5, resolve as resolve3 } from "node:path";
|
|
350
350
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
351
|
-
var AGENT_IDS = ["claude", "
|
|
351
|
+
var AGENT_IDS = ["claude", "universal", "agents-md"];
|
|
352
352
|
var SKILL_AGENTS_QUESTION = "Set up coding agents? (Space toggles, Enter confirms)";
|
|
353
|
+
var SKILL_DIRS = {
|
|
354
|
+
claude: ".claude/skills/nola",
|
|
355
|
+
universal: ".agents/skills/nola"
|
|
356
|
+
};
|
|
353
357
|
var AGENT_OPTIONS = [
|
|
354
|
-
{ value: "
|
|
355
|
-
{
|
|
356
|
-
|
|
357
|
-
|
|
358
|
+
{ value: "claude", label: "Claude Code", hint: ".claude/skills/nola/ (the only directory Claude Code reads)" },
|
|
359
|
+
{
|
|
360
|
+
value: "universal",
|
|
361
|
+
label: "Cursor, Copilot, Codex, Gemini CLI, \u2026",
|
|
362
|
+
hint: ".agents/skills/nola/ (the open Agent Skills location)"
|
|
363
|
+
},
|
|
364
|
+
{ value: "agents-md", label: "AGENTS.md", hint: "the skill body inline, for agents that read only AGENTS.md" }
|
|
358
365
|
];
|
|
359
366
|
var SKILLS_DIR = fileURLToPath3(new URL("../skills/nola/", import.meta.url));
|
|
360
367
|
var STAMP_RE = /^<!-- nola-skill v(\S+)/m;
|
|
361
368
|
var stamp = (version) => `<!-- nola-skill v${version} \u2014 regenerate with: nola skill install --force -->`;
|
|
369
|
+
var LEGACY_ADAPTERS = [".cursor/rules/nola.mdc", ".github/instructions/nola.instructions.md"];
|
|
362
370
|
async function readSkillSource() {
|
|
363
371
|
const full = (await readFile5(join5(SKILLS_DIR, "SKILL.md"), "utf8")).replaceAll("\r\n", "\n");
|
|
364
372
|
const fm = /^---\n([\s\S]*?)\n---\n/.exec(full);
|
|
@@ -370,27 +378,9 @@ async function readSkillSource() {
|
|
|
370
378
|
}
|
|
371
379
|
var LOCAL_REFERENCES = [
|
|
372
380
|
"Deeper references, when this repo has them:",
|
|
373
|
-
"`.claude/skills/nola/references/` \u2014
|
|
374
|
-
"pitfalls.md."
|
|
381
|
+
"`.agents/skills/nola/references/` or `.claude/skills/nola/references/` \u2014",
|
|
382
|
+
"syntax.md, patterns.md, config.md, pitfalls.md."
|
|
375
383
|
].join("\n");
|
|
376
|
-
function cursorRule(s, version) {
|
|
377
|
-
return [
|
|
378
|
-
"---",
|
|
379
|
-
"description: Nola (.tsi) language rules",
|
|
380
|
-
'globs: ["**/*.tsi", "nola.config.ts"]',
|
|
381
|
-
"alwaysApply: false",
|
|
382
|
-
"---",
|
|
383
|
-
stamp(version),
|
|
384
|
-
"",
|
|
385
|
-
s.body,
|
|
386
|
-
"",
|
|
387
|
-
LOCAL_REFERENCES,
|
|
388
|
-
""
|
|
389
|
-
].join("\n");
|
|
390
|
-
}
|
|
391
|
-
function copilotInstructions(s, version) {
|
|
392
|
-
return ["---", 'applyTo: "**/*.tsi"', "---", stamp(version), "", s.body, "", LOCAL_REFERENCES, ""].join("\n");
|
|
393
|
-
}
|
|
394
384
|
function agentsSection(s, version) {
|
|
395
385
|
return ["## Nola", "", stamp(version), "", s.body, "", LOCAL_REFERENCES, ""].join("\n");
|
|
396
386
|
}
|
|
@@ -399,16 +389,8 @@ function agentsMd(s, version) {
|
|
|
399
389
|
|
|
400
390
|
${agentsSection(s, version)}`;
|
|
401
391
|
}
|
|
402
|
-
function
|
|
403
|
-
|
|
404
|
-
const found = [];
|
|
405
|
-
if (existsSync5(join5(root, ".claude")) || existsSync5(join5(root, "CLAUDE.md"))) found.push("claude");
|
|
406
|
-
if (existsSync5(join5(root, ".cursor"))) found.push("cursor");
|
|
407
|
-
if (existsSync5(join5(root, ".github"))) found.push("copilot");
|
|
408
|
-
return found;
|
|
409
|
-
}
|
|
410
|
-
function defaultAgents(targetDir) {
|
|
411
|
-
return [.../* @__PURE__ */ new Set([...detectAgents(targetDir), "claude"])];
|
|
392
|
+
function defaultAgents(_targetDir) {
|
|
393
|
+
return ["claude", "universal"];
|
|
412
394
|
}
|
|
413
395
|
function parseAgentsFlag(value) {
|
|
414
396
|
if (value === "all") return [...AGENT_IDS];
|
|
@@ -438,16 +420,16 @@ function disposition(path, existing, version, force) {
|
|
|
438
420
|
stale: true
|
|
439
421
|
};
|
|
440
422
|
}
|
|
441
|
-
async function
|
|
442
|
-
const dir = join5(root,
|
|
423
|
+
async function writeSkillDir(root, base, s, version) {
|
|
424
|
+
const dir = join5(root, ...base.split("/"));
|
|
443
425
|
await mkdir3(join5(dir, "references"), { recursive: true });
|
|
444
426
|
const stamped = s.full.replace(/^(---\n[\s\S]*?\n---\n)/, `$1${stamp(version)}
|
|
445
427
|
`);
|
|
446
428
|
await writeFile3(join5(dir, "SKILL.md"), stamped);
|
|
447
|
-
const wrote = [
|
|
429
|
+
const wrote = [`${base}/SKILL.md`];
|
|
448
430
|
for (const name of (await readdir3(join5(SKILLS_DIR, "references"))).sort()) {
|
|
449
431
|
await writeFile3(join5(dir, "references", name), await readFile5(join5(SKILLS_DIR, "references", name), "utf8"));
|
|
450
|
-
wrote.push(
|
|
432
|
+
wrote.push(`${base}/references/${name}`);
|
|
451
433
|
}
|
|
452
434
|
return wrote;
|
|
453
435
|
}
|
|
@@ -457,51 +439,51 @@ async function writeAgentSkills(targetDir, agents, opts = {}) {
|
|
|
457
439
|
const source = await readSkillSource();
|
|
458
440
|
const force = opts.force === true;
|
|
459
441
|
const wrote = [];
|
|
442
|
+
const removed = [];
|
|
460
443
|
const skipped = [];
|
|
461
444
|
let stale = false;
|
|
462
|
-
const
|
|
463
|
-
cursor: { path: ".cursor/rules/nola.mdc", content: cursorRule(source, version) },
|
|
464
|
-
copilot: { path: ".github/instructions/nola.instructions.md", content: copilotInstructions(source, version) },
|
|
465
|
-
"agents-md": { path: "AGENTS.md", content: agentsMd(source, version) }
|
|
466
|
-
};
|
|
467
|
-
for (const id2 of AGENT_IDS) {
|
|
445
|
+
for (const id2 of ["claude", "universal"]) {
|
|
468
446
|
if (!agents.includes(id2)) continue;
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (!d.write) {
|
|
474
|
-
skipped.push(d.note);
|
|
475
|
-
stale ||= d.stale;
|
|
476
|
-
continue;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
wrote.push(...await writeClaudeSkill(root, source, version));
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
482
|
-
const { path, content } = flat[id2];
|
|
483
|
-
const abs = join5(root, ...path.split("/"));
|
|
484
|
-
if (existsSync5(abs)) {
|
|
485
|
-
if (id2 === "agents-md") {
|
|
486
|
-
skipped.push(
|
|
487
|
-
`AGENTS.md already exists \u2014 left untouched. Add this section manually:
|
|
488
|
-
|
|
489
|
-
${agentsSection(source, version)}`
|
|
490
|
-
);
|
|
491
|
-
continue;
|
|
492
|
-
}
|
|
493
|
-
const d = disposition(path, await readFile5(abs, "utf8"), version, force);
|
|
447
|
+
const base = SKILL_DIRS[id2];
|
|
448
|
+
const marker = join5(root, ...base.split("/"), "SKILL.md");
|
|
449
|
+
if (existsSync5(marker)) {
|
|
450
|
+
const d = disposition(`${base}/SKILL.md`, await readFile5(marker, "utf8"), version, force);
|
|
494
451
|
if (!d.write) {
|
|
495
452
|
skipped.push(d.note);
|
|
496
453
|
stale ||= d.stale;
|
|
497
454
|
continue;
|
|
498
455
|
}
|
|
499
456
|
}
|
|
500
|
-
await
|
|
501
|
-
|
|
502
|
-
|
|
457
|
+
wrote.push(...await writeSkillDir(root, base, source, version));
|
|
458
|
+
}
|
|
459
|
+
if (agents.includes("universal")) {
|
|
460
|
+
for (const path of LEGACY_ADAPTERS) {
|
|
461
|
+
const abs = join5(root, ...path.split("/"));
|
|
462
|
+
if (!existsSync5(abs) || !STAMP_RE.test(await readFile5(abs, "utf8"))) continue;
|
|
463
|
+
if (force) {
|
|
464
|
+
await rm3(abs);
|
|
465
|
+
removed.push(path);
|
|
466
|
+
} else {
|
|
467
|
+
skipped.push(`${path} is superseded by .agents/skills/nola \u2014 re-run with --force to remove it`);
|
|
468
|
+
stale = true;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (agents.includes("agents-md")) {
|
|
473
|
+
const abs = join5(root, "AGENTS.md");
|
|
474
|
+
if (existsSync5(abs)) {
|
|
475
|
+
skipped.push(
|
|
476
|
+
`AGENTS.md already exists \u2014 left untouched. Add this section manually:
|
|
477
|
+
|
|
478
|
+
${agentsSection(source, version)}`
|
|
479
|
+
);
|
|
480
|
+
} else {
|
|
481
|
+
await mkdir3(dirname2(abs), { recursive: true });
|
|
482
|
+
await writeFile3(abs, agentsMd(source, version));
|
|
483
|
+
wrote.push("AGENTS.md");
|
|
484
|
+
}
|
|
503
485
|
}
|
|
504
|
-
return { wrote, skipped, stale };
|
|
486
|
+
return { wrote, removed, skipped, stale };
|
|
505
487
|
}
|
|
506
488
|
|
|
507
489
|
// src/api.ts
|
|
@@ -1842,7 +1824,7 @@ async function runFlow(args, opts = {}) {
|
|
|
1842
1824
|
const result = await addNola(outcome.dir, { provider: outcome.provider });
|
|
1843
1825
|
const trial = grant ? await applyTrial(outcome.dir, { apiKey: grant.apiKey, hasConfig: !result.wrote.includes("nola.config.ts") }) : { wrote: [], skipped: [] };
|
|
1844
1826
|
const ide = outcome.ide === "vscode" ? await writeVscodeSetup(outcome.dir) : { wrote: [], skipped: [] };
|
|
1845
|
-
const ag = outcome.agents.length > 0 ? await writeAgentSkills(outcome.dir, outcome.agents) : { wrote: [], skipped: [] };
|
|
1827
|
+
const ag = outcome.agents.length > 0 ? await writeAgentSkills(outcome.dir, outcome.agents) : { wrote: [], removed: [], skipped: [] };
|
|
1846
1828
|
for (const note of [...result.skipped, ...trial.skipped, ...ide.skipped, ...ag.skipped]) prompter.note(note);
|
|
1847
1829
|
const trialWrote = trial.wrote.filter((f) => !result.wrote.includes(f));
|
|
1848
1830
|
const written = [...result.wrote, ...result.added, ...trialWrote, ...ide.wrote, ...ag.wrote];
|
|
@@ -1931,7 +1913,6 @@ export {
|
|
|
1931
1913
|
AGENT_IDS,
|
|
1932
1914
|
SKILL_AGENTS_QUESTION,
|
|
1933
1915
|
AGENT_OPTIONS,
|
|
1934
|
-
detectAgents,
|
|
1935
1916
|
defaultAgents,
|
|
1936
1917
|
parseAgentsFlag,
|
|
1937
1918
|
writeAgentSkills,
|
package/dist/flow.js
CHANGED
|
@@ -50,7 +50,7 @@ function templateOption(t) {
|
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
52
|
* The setup step: a yes/no gate (default yes), then editor + coding agents in
|
|
53
|
-
* one grouped checkbox list, VS Code and
|
|
53
|
+
* one grouped checkbox list, VS Code and both skill copies preselected — so the
|
|
54
54
|
* defaults are two Enters and the whole no is one. `--ide` / `--agents` each
|
|
55
55
|
* fill their half — a group a flag already answered is not shown and the gate
|
|
56
56
|
* narrows to the half that remains; a no leaves the asked halves at none.
|
|
@@ -518,7 +518,9 @@ export async function runFlow(args, opts = {}) {
|
|
|
518
518
|
: { wrote: [], skipped: [] };
|
|
519
519
|
// An explicit editor choice is honored even when Nola itself was already set up.
|
|
520
520
|
const ide = outcome.ide === "vscode" ? await writeVscodeSetup(outcome.dir) : { wrote: [], skipped: [] };
|
|
521
|
-
const ag = outcome.agents.length > 0
|
|
521
|
+
const ag = outcome.agents.length > 0
|
|
522
|
+
? await writeAgentSkills(outcome.dir, outcome.agents)
|
|
523
|
+
: { wrote: [], removed: [], skipped: [] };
|
|
522
524
|
for (const note of [...result.skipped, ...trial.skipped, ...ide.skipped, ...ag.skipped])
|
|
523
525
|
prompter.note(note);
|
|
524
526
|
// applyTrial re-writes the config addNola just wrote — list it once.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { AddOptions, AddResult } from "./add.js";
|
|
2
2
|
export { addNola } from "./add.js";
|
|
3
3
|
export type { AgentId, AgentSetupResult } from "./agents.js";
|
|
4
|
-
export { AGENT_IDS, AGENT_OPTIONS, defaultAgents,
|
|
4
|
+
export { AGENT_IDS, AGENT_OPTIONS, defaultAgents, parseAgentsFlag, writeAgentSkills } from "./agents.js";
|
|
5
5
|
export type { ClientInit, KeyGrant, NolaAccountKind, NolaAuthConfig, NolaBillingSession, NolaCapabilities, NolaClaim, NolaConsoleCreatedKey, NolaConsoleMe, NolaTrialResponse, } from "./api.js";
|
|
6
6
|
export { claimSession, createBillingSession, createConsoleKey, DEFAULT_API_URL, fetchCapabilities, fetchMe, isSessionRejected, NolaApiError, nolaApiUrl, requestTrial, sessionIdOf, } from "./api.js";
|
|
7
7
|
export type { AuthOptions, SignInOptions, TokenSet } from "./auth.js";
|
package/dist/index.js
CHANGED
|
@@ -39,7 +39,6 @@ import {
|
|
|
39
39
|
defaultAgents,
|
|
40
40
|
defineCommand,
|
|
41
41
|
deleteSession,
|
|
42
|
-
detectAgents,
|
|
43
42
|
devExamplesDir,
|
|
44
43
|
dispatch,
|
|
45
44
|
ensureEnvIgnored,
|
|
@@ -86,7 +85,7 @@ import {
|
|
|
86
85
|
writeEnvKey,
|
|
87
86
|
writeSession,
|
|
88
87
|
writeVscodeSetup
|
|
89
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-GUYF4NM5.js";
|
|
90
89
|
import {
|
|
91
90
|
clackPrompter
|
|
92
91
|
} from "./chunk-7IQIRMK6.js";
|
|
@@ -109,7 +108,7 @@ async function runSkillInstall(args, opts = {}) {
|
|
|
109
108
|
if (args.agents !== void 0) {
|
|
110
109
|
agents = parseAgentsFlag(args.agents);
|
|
111
110
|
} else if (!interactive) {
|
|
112
|
-
prompter.note(
|
|
111
|
+
prompter.note(`non-interactive: pass --agents (${AGENT_IDS.join(", ")} \u2014 or all, none)`);
|
|
113
112
|
return 1;
|
|
114
113
|
} else {
|
|
115
114
|
const choice = await prompter.multiselect(SKILL_AGENTS_QUESTION, AGENT_OPTIONS, defaultAgents(dir));
|
|
@@ -125,6 +124,7 @@ async function runSkillInstall(args, opts = {}) {
|
|
|
125
124
|
}
|
|
126
125
|
const result = await writeAgentSkills(dir, agents, { force: args.force });
|
|
127
126
|
for (const note of result.skipped) prompter.note(note);
|
|
127
|
+
if (result.removed.length > 0) prompter.note(`Removed superseded: ${result.removed.join(", ")}`);
|
|
128
128
|
let message = "Nothing new to write.";
|
|
129
129
|
if (result.wrote.length > 0) message = `Installed agent skill files: ${result.wrote.join(", ")}`;
|
|
130
130
|
else if (result.stale) message = "Nothing written \u2014 re-run with --force to replace outdated files.";
|
|
@@ -173,7 +173,6 @@ export {
|
|
|
173
173
|
defaultAgents,
|
|
174
174
|
defineCommand,
|
|
175
175
|
deleteSession,
|
|
176
|
-
detectAgents,
|
|
177
176
|
devExamplesDir,
|
|
178
177
|
dispatch,
|
|
179
178
|
ensureEnvIgnored,
|
package/dist/main.js
CHANGED
package/dist/skill-install.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AGENT_OPTIONS, defaultAgents, parseAgentsFlag, SKILL_AGENTS_QUESTION, writeAgentSkills } from "./agents.js";
|
|
1
|
+
import { AGENT_IDS, AGENT_OPTIONS, defaultAgents, parseAgentsFlag, SKILL_AGENTS_QUESTION, writeAgentSkills, } from "./agents.js";
|
|
2
2
|
import { plainPrompter } from "./flow.js";
|
|
3
3
|
/** The `nola skill install` entry — same delegation shape as `nola init` → runFlow. */
|
|
4
4
|
export async function runSkillInstall(args, opts = {}) {
|
|
@@ -19,7 +19,7 @@ export async function runSkillInstall(args, opts = {}) {
|
|
|
19
19
|
agents = parseAgentsFlag(args.agents);
|
|
20
20
|
}
|
|
21
21
|
else if (!interactive) {
|
|
22
|
-
prompter.note(
|
|
22
|
+
prompter.note(`non-interactive: pass --agents (${AGENT_IDS.join(", ")} — or all, none)`);
|
|
23
23
|
return 1;
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
@@ -37,6 +37,8 @@ export async function runSkillInstall(args, opts = {}) {
|
|
|
37
37
|
const result = await writeAgentSkills(dir, agents, { force: args.force });
|
|
38
38
|
for (const note of result.skipped)
|
|
39
39
|
prompter.note(note);
|
|
40
|
+
if (result.removed.length > 0)
|
|
41
|
+
prompter.note(`Removed superseded: ${result.removed.join(", ")}`);
|
|
40
42
|
let message = "Nothing new to write.";
|
|
41
43
|
if (result.wrote.length > 0)
|
|
42
44
|
message = `Installed agent skill files: ${result.wrote.join(", ")}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-nola-lang",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Scaffold a new Nola project (npm create nola, or npm create nola-lang)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nola",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"files": [
|
|
39
39
|
"dist",
|
|
40
40
|
"!dist/.tsbuildinfo",
|
|
41
|
+
"!dist/**/*.map",
|
|
41
42
|
"templates",
|
|
42
43
|
"skills"
|
|
43
44
|
],
|
package/skills/nola/SKILL.md
CHANGED
|
@@ -61,9 +61,25 @@ infer function extractPerson(.text: string) {
|
|
|
61
61
|
`import { f } from "./x.tsi"`. Imports of plain TS use NodeNext style:
|
|
62
62
|
`import { g } from "./y.js"` (the `.js` extension, even though the source
|
|
63
63
|
file is `.ts`).
|
|
64
|
-
-
|
|
65
|
-
(
|
|
66
|
-
`
|
|
64
|
+
- Every `export type` / `export interface` in a `.tsi` is ALSO a runtime
|
|
65
|
+
value (`User.toJsonSchema()`, `User.validate(v)`, `User.parse(v)`,
|
|
66
|
+
`User["~standard"]` — Standard Schema AND Standard JSON Schema, so
|
|
67
|
+
`jsonSchema.input({ target: "openapi-3.0" })` works). Never declare a
|
|
68
|
+
`const`/`function`/`class`/`enum`
|
|
69
|
+
with an exported type's name (NOLA2011). `./x.tsi` with no `x.tsi` on disk
|
|
70
|
+
is the VIEW of `x.ts`: the same module plus those values — import plain-TS
|
|
71
|
+
types that way when you need their schema. Keep one basename per module.
|
|
72
|
+
- An extractor's `<T>` should be a named, JSON-shaped type. The schema comes
|
|
73
|
+
from the RESOLVED type, so unions (a `kind` key makes them discriminated),
|
|
74
|
+
`Partial<T>`, `extends`, `Record<string, T>` and types from packages all
|
|
75
|
+
work; `Date` is revived to a real `Date`. `Map`/`Set`/`Promise`, functions
|
|
76
|
+
and a generic used without arguments do not derive. A failed reply is
|
|
77
|
+
corrected with EVERY validation issue at once.
|
|
78
|
+
- Constrain a member with JSDoc tags named like JSON Schema keywords —
|
|
79
|
+
`/** @format email */`, `/** @integer @minimum 13 */`, `/** @minItems 1 */`,
|
|
80
|
+
`@pattern`, `@minLength`, `@uniqueItems`, … — the model sees them and
|
|
81
|
+
`validate` enforces them. A tag on the wrong kind of type is NOLA2012.
|
|
82
|
+
See `references/syntax.md` → "Types as values".
|
|
67
83
|
- Raw extract/call intents are resolved with `ask`, never bare `await`
|
|
68
84
|
(that throws NOLA3010). Infer-function RETURN values may be awaited.
|
|
69
85
|
|