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.
Files changed (68) hide show
  1. package/dist/agents.d.ts +16 -10
  2. package/dist/agents.js +72 -80
  3. package/dist/{chunk-34XSPT6F.js → chunk-GUYF4NM5.js} +58 -77
  4. package/dist/flow.js +4 -2
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.js +3 -4
  7. package/dist/main.js +1 -1
  8. package/dist/skill-install.js +4 -2
  9. package/package.json +2 -1
  10. package/skills/nola/SKILL.md +19 -3
  11. package/skills/nola/references/config.md +3 -3
  12. package/skills/nola/references/patterns.md +13 -1
  13. package/skills/nola/references/pitfalls.md +12 -10
  14. package/skills/nola/references/syntax.md +80 -7
  15. package/dist/add.d.ts.map +0 -1
  16. package/dist/add.js.map +0 -1
  17. package/dist/agents.d.ts.map +0 -1
  18. package/dist/agents.js.map +0 -1
  19. package/dist/api.d.ts.map +0 -1
  20. package/dist/api.js.map +0 -1
  21. package/dist/auth.d.ts.map +0 -1
  22. package/dist/auth.js.map +0 -1
  23. package/dist/checkout.d.ts.map +0 -1
  24. package/dist/checkout.js.map +0 -1
  25. package/dist/cli.d.ts.map +0 -1
  26. package/dist/cli.js.map +0 -1
  27. package/dist/credentials.d.ts.map +0 -1
  28. package/dist/credentials.js.map +0 -1
  29. package/dist/examples.d.ts.map +0 -1
  30. package/dist/examples.js.map +0 -1
  31. package/dist/flow.d.ts.map +0 -1
  32. package/dist/flow.js.map +0 -1
  33. package/dist/github.d.ts.map +0 -1
  34. package/dist/github.js.map +0 -1
  35. package/dist/home-config.d.ts.map +0 -1
  36. package/dist/home-config.js.map +0 -1
  37. package/dist/ide.d.ts.map +0 -1
  38. package/dist/ide.js.map +0 -1
  39. package/dist/index.d.ts.map +0 -1
  40. package/dist/index.js.map +0 -1
  41. package/dist/key.d.ts.map +0 -1
  42. package/dist/key.js.map +0 -1
  43. package/dist/launch.d.ts.map +0 -1
  44. package/dist/launch.js.map +0 -1
  45. package/dist/main.d.ts.map +0 -1
  46. package/dist/main.js.map +0 -1
  47. package/dist/node-version.d.ts.map +0 -1
  48. package/dist/node-version.js.map +0 -1
  49. package/dist/open-url.d.ts.map +0 -1
  50. package/dist/open-url.js.map +0 -1
  51. package/dist/package-manager.d.ts.map +0 -1
  52. package/dist/package-manager.js.map +0 -1
  53. package/dist/palette.d.ts.map +0 -1
  54. package/dist/palette.js.map +0 -1
  55. package/dist/prompter.d.ts.map +0 -1
  56. package/dist/prompter.js.map +0 -1
  57. package/dist/providers.d.ts.map +0 -1
  58. package/dist/providers.js.map +0 -1
  59. package/dist/registry.d.ts.map +0 -1
  60. package/dist/registry.js.map +0 -1
  61. package/dist/scaffold.d.ts.map +0 -1
  62. package/dist/scaffold.js.map +0 -1
  63. package/dist/skill-install.d.ts.map +0 -1
  64. package/dist/skill-install.js.map +0 -1
  65. package/dist/style.d.ts.map +0 -1
  66. package/dist/style.js.map +0 -1
  67. package/dist/trial.d.ts.map +0 -1
  68. package/dist/trial.js.map +0 -1
package/dist/agents.d.ts CHANGED
@@ -1,7 +1,14 @@
1
- export type AgentId = "claude" | "cursor" | "copilot" | "agents-md";
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
- * Adapters that inject context inline embed SKILL.md's BODY verbatim, so no
13
- * adapter restates the language rules in its own words and the four can
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 claude adapter copies */
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
- /** Agents whose tooling is already present in the project. */
28
- export declare function detectAgents(targetDir: string): AgentId[];
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 adapters as self-contained, version-stamped content.
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", "cursor", "copilot", "agents-md"];
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: "agents-md", label: "AGENTS.md", hint: "cross-agent instructions (Codex, Gemini CLI, …)" },
12
- { value: "claude", label: "Claude Code", hint: ".claude/skills/nola/ (full skill + references)" },
13
- { value: "cursor", label: "Cursor", hint: ".cursor/rules/nola.mdc" },
14
- { value: "copilot", label: "GitHub Copilot", hint: ".github/instructions/nola.instructions.md" },
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 copy — never node_modules (spec §1). */
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/` — syntax.md, patterns.md, config.md,",
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
- /** Agents whose tooling is already present in the project. */
67
- export function detectAgents(targetDir) {
68
- const root = resolve(targetDir);
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 .claude/skills/nola/. */
120
- async function writeClaudeSkill(root, s, version) {
121
- const dir = join(root, ".claude", "skills", "nola");
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 = [".claude/skills/nola/SKILL.md"];
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(`.claude/skills/nola/references/${name}`);
116
+ wrote.push(`${base}/references/${name}`);
129
117
  }
130
118
  return wrote;
131
119
  }
132
120
  /**
133
- * Write the selected adapters as self-contained, version-stamped content.
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 flat = {
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
- if (id === "claude") {
153
- const marker = join(root, ".claude", "skills", "nola", "SKILL.md");
154
- if (existsSync(marker)) {
155
- const d = disposition(".claude/skills/nola/SKILL.md", await readFile(marker, "utf8"), version, force);
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 mkdir(dirname(abs), { recursive: true });
181
- await writeFile(abs, content);
182
- wrote.push(path);
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", "cursor", "copilot", "agents-md"];
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: "agents-md", label: "AGENTS.md", hint: "cross-agent instructions (Codex, Gemini CLI, \u2026)" },
355
- { value: "claude", label: "Claude Code", hint: ".claude/skills/nola/ (full skill + references)" },
356
- { value: "cursor", label: "Cursor", hint: ".cursor/rules/nola.mdc" },
357
- { value: "copilot", label: "GitHub Copilot", hint: ".github/instructions/nola.instructions.md" }
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 syntax.md, patterns.md, config.md,",
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 detectAgents(targetDir) {
403
- const root = resolve3(targetDir);
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 writeClaudeSkill(root, s, version) {
442
- const dir = join5(root, ".claude", "skills", "nola");
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 = [".claude/skills/nola/SKILL.md"];
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(`.claude/skills/nola/references/${name}`);
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 flat = {
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
- if (id2 === "claude") {
470
- const marker = join5(root, ".claude", "skills", "nola", "SKILL.md");
471
- if (existsSync5(marker)) {
472
- const d = disposition(".claude/skills/nola/SKILL.md", await readFile5(marker, "utf8"), version, force);
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 mkdir3(dirname2(abs), { recursive: true });
501
- await writeFile3(abs, content);
502
- wrote.push(path);
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 Claude Code preselected — so the
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 ? await writeAgentSkills(outcome.dir, outcome.agents) : { wrote: [], skipped: [] };
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, detectAgents, parseAgentsFlag, writeAgentSkills } from "./agents.js";
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-34XSPT6F.js";
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("non-interactive: pass --agents (claude, cursor, copilot, agents-md \u2014 or all, none)");
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
@@ -3,7 +3,7 @@ import {
3
3
  CREATE_COMMAND,
4
4
  dispatch,
5
5
  ownVersion
6
- } from "./chunk-34XSPT6F.js";
6
+ } from "./chunk-GUYF4NM5.js";
7
7
  import "./chunk-FPS2V3S4.js";
8
8
 
9
9
  // src/main.ts
@@ -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("non-interactive: pass --agents (claude, cursor, copilot, agents-md — or all, none)");
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.6",
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
  ],
@@ -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
- - An extractor's `<T>` should be a named, structurally simple type
65
- (object/array/primitive fields). `Date` works (revived to a real `Date`);
66
- `Map`/`Set` and other ambient lib types do not.
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