create-nola-lang 0.1.7 → 0.1.9
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-KCGD4VJM.js} +73 -85
- package/dist/flow.js +17 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -4
- package/dist/main.js +1 -1
- package/dist/registry.d.ts +10 -0
- package/dist/registry.js +6 -0
- 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 +11 -4
- 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
|
|
@@ -151,7 +151,13 @@ var TEMPLATES = [
|
|
|
151
151
|
{ name: "extract-invoice", label: "same-file type references, optional fields", source: "example" },
|
|
152
152
|
{ name: "classify-message", label: "closed label sets: union alias, string enum, inline union", source: "example" },
|
|
153
153
|
{ name: "chain-of-thought", label: "two asks sharing accumulating context", source: "example" },
|
|
154
|
-
{ name: "research-notes", label: "TS control flow orchestrating nola functions", source: "example" }
|
|
154
|
+
{ name: "research-notes", label: "TS control flow orchestrating nola functions", source: "example" },
|
|
155
|
+
{
|
|
156
|
+
name: "triage-ticket",
|
|
157
|
+
label: "ticket triage: literal unions and booleans as typed questions; no provider question",
|
|
158
|
+
source: "example",
|
|
159
|
+
provider: { label: "typesafe.ai", envVar: "TYPESAFE_API_KEY" }
|
|
160
|
+
}
|
|
155
161
|
];
|
|
156
162
|
function templateByName(name) {
|
|
157
163
|
return TEMPLATES.find((t) => t.name === name);
|
|
@@ -345,20 +351,28 @@ async function addNola(targetDir, opts = {}) {
|
|
|
345
351
|
|
|
346
352
|
// src/agents.ts
|
|
347
353
|
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";
|
|
354
|
+
import { mkdir as mkdir3, readdir as readdir3, readFile as readFile5, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
|
|
349
355
|
import { dirname as dirname2, join as join5, resolve as resolve3 } from "node:path";
|
|
350
356
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
351
|
-
var AGENT_IDS = ["claude", "
|
|
357
|
+
var AGENT_IDS = ["claude", "universal", "agents-md"];
|
|
352
358
|
var SKILL_AGENTS_QUESTION = "Set up coding agents? (Space toggles, Enter confirms)";
|
|
359
|
+
var SKILL_DIRS = {
|
|
360
|
+
claude: ".claude/skills/nola",
|
|
361
|
+
universal: ".agents/skills/nola"
|
|
362
|
+
};
|
|
353
363
|
var AGENT_OPTIONS = [
|
|
354
|
-
{ value: "
|
|
355
|
-
{
|
|
356
|
-
|
|
357
|
-
|
|
364
|
+
{ value: "claude", label: "Claude Code", hint: ".claude/skills/nola/ (the only directory Claude Code reads)" },
|
|
365
|
+
{
|
|
366
|
+
value: "universal",
|
|
367
|
+
label: "Cursor, Copilot, Codex, Gemini CLI, \u2026",
|
|
368
|
+
hint: ".agents/skills/nola/ (the open Agent Skills location)"
|
|
369
|
+
},
|
|
370
|
+
{ value: "agents-md", label: "AGENTS.md", hint: "the skill body inline, for agents that read only AGENTS.md" }
|
|
358
371
|
];
|
|
359
372
|
var SKILLS_DIR = fileURLToPath3(new URL("../skills/nola/", import.meta.url));
|
|
360
373
|
var STAMP_RE = /^<!-- nola-skill v(\S+)/m;
|
|
361
374
|
var stamp = (version) => `<!-- nola-skill v${version} \u2014 regenerate with: nola skill install --force -->`;
|
|
375
|
+
var LEGACY_ADAPTERS = [".cursor/rules/nola.mdc", ".github/instructions/nola.instructions.md"];
|
|
362
376
|
async function readSkillSource() {
|
|
363
377
|
const full = (await readFile5(join5(SKILLS_DIR, "SKILL.md"), "utf8")).replaceAll("\r\n", "\n");
|
|
364
378
|
const fm = /^---\n([\s\S]*?)\n---\n/.exec(full);
|
|
@@ -370,27 +384,9 @@ async function readSkillSource() {
|
|
|
370
384
|
}
|
|
371
385
|
var LOCAL_REFERENCES = [
|
|
372
386
|
"Deeper references, when this repo has them:",
|
|
373
|
-
"`.claude/skills/nola/references/` \u2014
|
|
374
|
-
"pitfalls.md."
|
|
387
|
+
"`.agents/skills/nola/references/` or `.claude/skills/nola/references/` \u2014",
|
|
388
|
+
"syntax.md, patterns.md, config.md, pitfalls.md."
|
|
375
389
|
].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
390
|
function agentsSection(s, version) {
|
|
395
391
|
return ["## Nola", "", stamp(version), "", s.body, "", LOCAL_REFERENCES, ""].join("\n");
|
|
396
392
|
}
|
|
@@ -399,16 +395,8 @@ function agentsMd(s, version) {
|
|
|
399
395
|
|
|
400
396
|
${agentsSection(s, version)}`;
|
|
401
397
|
}
|
|
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"])];
|
|
398
|
+
function defaultAgents(_targetDir) {
|
|
399
|
+
return ["claude", "universal"];
|
|
412
400
|
}
|
|
413
401
|
function parseAgentsFlag(value) {
|
|
414
402
|
if (value === "all") return [...AGENT_IDS];
|
|
@@ -438,16 +426,16 @@ function disposition(path, existing, version, force) {
|
|
|
438
426
|
stale: true
|
|
439
427
|
};
|
|
440
428
|
}
|
|
441
|
-
async function
|
|
442
|
-
const dir = join5(root,
|
|
429
|
+
async function writeSkillDir(root, base, s, version) {
|
|
430
|
+
const dir = join5(root, ...base.split("/"));
|
|
443
431
|
await mkdir3(join5(dir, "references"), { recursive: true });
|
|
444
432
|
const stamped = s.full.replace(/^(---\n[\s\S]*?\n---\n)/, `$1${stamp(version)}
|
|
445
433
|
`);
|
|
446
434
|
await writeFile3(join5(dir, "SKILL.md"), stamped);
|
|
447
|
-
const wrote = [
|
|
435
|
+
const wrote = [`${base}/SKILL.md`];
|
|
448
436
|
for (const name of (await readdir3(join5(SKILLS_DIR, "references"))).sort()) {
|
|
449
437
|
await writeFile3(join5(dir, "references", name), await readFile5(join5(SKILLS_DIR, "references", name), "utf8"));
|
|
450
|
-
wrote.push(
|
|
438
|
+
wrote.push(`${base}/references/${name}`);
|
|
451
439
|
}
|
|
452
440
|
return wrote;
|
|
453
441
|
}
|
|
@@ -457,51 +445,51 @@ async function writeAgentSkills(targetDir, agents, opts = {}) {
|
|
|
457
445
|
const source = await readSkillSource();
|
|
458
446
|
const force = opts.force === true;
|
|
459
447
|
const wrote = [];
|
|
448
|
+
const removed = [];
|
|
460
449
|
const skipped = [];
|
|
461
450
|
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) {
|
|
451
|
+
for (const id2 of ["claude", "universal"]) {
|
|
468
452
|
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);
|
|
453
|
+
const base = SKILL_DIRS[id2];
|
|
454
|
+
const marker = join5(root, ...base.split("/"), "SKILL.md");
|
|
455
|
+
if (existsSync5(marker)) {
|
|
456
|
+
const d = disposition(`${base}/SKILL.md`, await readFile5(marker, "utf8"), version, force);
|
|
494
457
|
if (!d.write) {
|
|
495
458
|
skipped.push(d.note);
|
|
496
459
|
stale ||= d.stale;
|
|
497
460
|
continue;
|
|
498
461
|
}
|
|
499
462
|
}
|
|
500
|
-
await
|
|
501
|
-
|
|
502
|
-
|
|
463
|
+
wrote.push(...await writeSkillDir(root, base, source, version));
|
|
464
|
+
}
|
|
465
|
+
if (agents.includes("universal")) {
|
|
466
|
+
for (const path of LEGACY_ADAPTERS) {
|
|
467
|
+
const abs = join5(root, ...path.split("/"));
|
|
468
|
+
if (!existsSync5(abs) || !STAMP_RE.test(await readFile5(abs, "utf8"))) continue;
|
|
469
|
+
if (force) {
|
|
470
|
+
await rm3(abs);
|
|
471
|
+
removed.push(path);
|
|
472
|
+
} else {
|
|
473
|
+
skipped.push(`${path} is superseded by .agents/skills/nola \u2014 re-run with --force to remove it`);
|
|
474
|
+
stale = true;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (agents.includes("agents-md")) {
|
|
479
|
+
const abs = join5(root, "AGENTS.md");
|
|
480
|
+
if (existsSync5(abs)) {
|
|
481
|
+
skipped.push(
|
|
482
|
+
`AGENTS.md already exists \u2014 left untouched. Add this section manually:
|
|
483
|
+
|
|
484
|
+
${agentsSection(source, version)}`
|
|
485
|
+
);
|
|
486
|
+
} else {
|
|
487
|
+
await mkdir3(dirname2(abs), { recursive: true });
|
|
488
|
+
await writeFile3(abs, agentsMd(source, version));
|
|
489
|
+
wrote.push("AGENTS.md");
|
|
490
|
+
}
|
|
503
491
|
}
|
|
504
|
-
return { wrote, skipped, stale };
|
|
492
|
+
return { wrote, removed, skipped, stale };
|
|
505
493
|
}
|
|
506
494
|
|
|
507
495
|
// src/api.ts
|
|
@@ -1483,7 +1471,8 @@ var EDITOR_OPTIONS = [
|
|
|
1483
1471
|
{ value: "vscode", label: "VS Code", hint: ".vscode/ with a debug launch config + extension recommendation" }
|
|
1484
1472
|
];
|
|
1485
1473
|
function templateOption(t) {
|
|
1486
|
-
|
|
1474
|
+
const vendor = t.provider ? ` (${t.provider.label})` : "";
|
|
1475
|
+
return { value: t.name, label: t.source === "example" ? `example: ${t.name}${vendor}` : `${t.name}${vendor}`, hint: t.label };
|
|
1487
1476
|
}
|
|
1488
1477
|
async function resolveSetup(input, prompter, dir) {
|
|
1489
1478
|
const ide = input.ide === "vscode" || input.ide === "none" ? input.ide : void 0;
|
|
@@ -1537,10 +1526,10 @@ function flaggedProvider(input) {
|
|
|
1537
1526
|
}
|
|
1538
1527
|
return input.provider;
|
|
1539
1528
|
}
|
|
1540
|
-
async function resolveProvider(input, prompter) {
|
|
1529
|
+
async function resolveProvider(input, prompter, pinned = false) {
|
|
1541
1530
|
const flagged = flaggedProvider(input);
|
|
1542
1531
|
if (flagged !== void 0) return flagged;
|
|
1543
|
-
if (!input.interactive) return "none";
|
|
1532
|
+
if (pinned || !input.interactive) return "none";
|
|
1544
1533
|
const path = input.keyPath ?? { kind: "trial" };
|
|
1545
1534
|
while (true) {
|
|
1546
1535
|
const choice = await prompter.select(PROVIDER_QUESTION, providerOptions(path));
|
|
@@ -1550,8 +1539,8 @@ async function resolveProvider(input, prompter) {
|
|
|
1550
1539
|
return choice;
|
|
1551
1540
|
}
|
|
1552
1541
|
}
|
|
1553
|
-
async function resolveExtras(input, prompter, dir) {
|
|
1554
|
-
const provider = await resolveProvider(input, prompter);
|
|
1542
|
+
async function resolveExtras(input, prompter, dir, pinnedProvider = false) {
|
|
1543
|
+
const provider = await resolveProvider(input, prompter, pinnedProvider);
|
|
1555
1544
|
if (provider === null) return null;
|
|
1556
1545
|
const setup = await resolveSetup(input, prompter, dir);
|
|
1557
1546
|
return setup === null ? null : { provider, ...setup };
|
|
@@ -1648,7 +1637,7 @@ async function resolveScaffoldOptions(input, prompter) {
|
|
|
1648
1637
|
template = choice;
|
|
1649
1638
|
}
|
|
1650
1639
|
}
|
|
1651
|
-
const extras = await resolveExtras(input, prompter, dir);
|
|
1640
|
+
const extras = await resolveExtras(input, prompter, dir, templateByName(template)?.provider !== void 0);
|
|
1652
1641
|
if (extras === null) return { kind: "cancelled" };
|
|
1653
1642
|
return { kind: "scaffold", dir, template, force, ...extras };
|
|
1654
1643
|
}
|
|
@@ -1697,7 +1686,7 @@ function commented(command, note) {
|
|
|
1697
1686
|
}
|
|
1698
1687
|
function nextSteps(outcome, fileCount, name, pm, installed = false, grant = null) {
|
|
1699
1688
|
const cmd = packageManagerCommands(pm);
|
|
1700
|
-
const vendorEnv = providerById(outcome.provider)?.envVar;
|
|
1689
|
+
const vendorEnv = providerById(outcome.provider)?.envVar ?? (outcome.provider === "none" ? templateByName(outcome.template)?.provider?.envVar : void 0);
|
|
1701
1690
|
const startNote = grant ? grant.source === "trial" ? "25 free Nola runs \u2014 key in .env" : "key in .env (run `npx nola-lang account` to check the balance)" : vendorEnv ? `set ${vendorEnv} in .env first` : outcome.template === "empty" ? "set OPENAI_API_KEY first" : "runs offline \u2014 no API key needed";
|
|
1702
1691
|
const lines = [
|
|
1703
1692
|
...outcome.dir === "." ? [] : [`cd ${outcome.dir}`],
|
|
@@ -1842,7 +1831,7 @@ async function runFlow(args, opts = {}) {
|
|
|
1842
1831
|
const result = await addNola(outcome.dir, { provider: outcome.provider });
|
|
1843
1832
|
const trial = grant ? await applyTrial(outcome.dir, { apiKey: grant.apiKey, hasConfig: !result.wrote.includes("nola.config.ts") }) : { wrote: [], skipped: [] };
|
|
1844
1833
|
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: [] };
|
|
1834
|
+
const ag = outcome.agents.length > 0 ? await writeAgentSkills(outcome.dir, outcome.agents) : { wrote: [], removed: [], skipped: [] };
|
|
1846
1835
|
for (const note of [...result.skipped, ...trial.skipped, ...ide.skipped, ...ag.skipped]) prompter.note(note);
|
|
1847
1836
|
const trialWrote = trial.wrote.filter((f) => !result.wrote.includes(f));
|
|
1848
1837
|
const written = [...result.wrote, ...result.added, ...trialWrote, ...ide.wrote, ...ag.wrote];
|
|
@@ -1931,7 +1920,6 @@ export {
|
|
|
1931
1920
|
AGENT_IDS,
|
|
1932
1921
|
SKILL_AGENTS_QUESTION,
|
|
1933
1922
|
AGENT_OPTIONS,
|
|
1934
|
-
detectAgents,
|
|
1935
1923
|
defaultAgents,
|
|
1936
1924
|
parseAgentsFlag,
|
|
1937
1925
|
writeAgentSkills,
|
package/dist/flow.js
CHANGED
|
@@ -46,11 +46,13 @@ const EDITOR_OPTIONS = [
|
|
|
46
46
|
{ value: "vscode", label: "VS Code", hint: ".vscode/ with a debug launch config + extension recommendation" },
|
|
47
47
|
];
|
|
48
48
|
function templateOption(t) {
|
|
49
|
-
|
|
49
|
+
// a pinned template names its vendor in the row itself: `example: triage-ticket (typesafe.ai)`
|
|
50
|
+
const vendor = t.provider ? ` (${t.provider.label})` : "";
|
|
51
|
+
return { value: t.name, label: t.source === "example" ? `example: ${t.name}${vendor}` : `${t.name}${vendor}`, hint: t.label };
|
|
50
52
|
}
|
|
51
53
|
/**
|
|
52
54
|
* The setup step: a yes/no gate (default yes), then editor + coding agents in
|
|
53
|
-
* one grouped checkbox list, VS Code and
|
|
55
|
+
* one grouped checkbox list, VS Code and both skill copies preselected — so the
|
|
54
56
|
* defaults are two Enters and the whole no is one. `--ide` / `--agents` each
|
|
55
57
|
* fill their half — a group a flag already answered is not shown and the gate
|
|
56
58
|
* narrows to the half that remains; a no leaves the asked halves at none.
|
|
@@ -133,11 +135,13 @@ function flaggedProvider(input) {
|
|
|
133
135
|
* one Enter away, Ctrl+C cancels). The key itself is minted later, on the
|
|
134
136
|
* stored session, after every question — nothing is consumed by a cancel.
|
|
135
137
|
*/
|
|
136
|
-
async function resolveProvider(input, prompter) {
|
|
138
|
+
async function resolveProvider(input, prompter, pinned = false) {
|
|
137
139
|
const flagged = flaggedProvider(input);
|
|
138
140
|
if (flagged !== undefined)
|
|
139
141
|
return flagged;
|
|
140
|
-
|
|
142
|
+
// A template whose own config names its vendor (triage-ticket → typesafe())
|
|
143
|
+
// is not asked: "none" keeps that config, and the outro names its env var.
|
|
144
|
+
if (pinned || !input.interactive)
|
|
141
145
|
return "none";
|
|
142
146
|
const path = input.keyPath ?? { kind: "trial" };
|
|
143
147
|
while (true) {
|
|
@@ -152,8 +156,8 @@ async function resolveProvider(input, prompter) {
|
|
|
152
156
|
}
|
|
153
157
|
}
|
|
154
158
|
/** The provider and setup (editor + agents) questions, in that order, shared by every outcome site; null = cancelled. */
|
|
155
|
-
async function resolveExtras(input, prompter, dir) {
|
|
156
|
-
const provider = await resolveProvider(input, prompter);
|
|
159
|
+
async function resolveExtras(input, prompter, dir, pinnedProvider = false) {
|
|
160
|
+
const provider = await resolveProvider(input, prompter, pinnedProvider);
|
|
157
161
|
if (provider === null)
|
|
158
162
|
return null;
|
|
159
163
|
const setup = await resolveSetup(input, prompter, dir);
|
|
@@ -263,7 +267,7 @@ export async function resolveScaffoldOptions(input, prompter) {
|
|
|
263
267
|
template = choice;
|
|
264
268
|
}
|
|
265
269
|
}
|
|
266
|
-
const extras = await resolveExtras(input, prompter, dir);
|
|
270
|
+
const extras = await resolveExtras(input, prompter, dir, templateByName(template)?.provider !== undefined);
|
|
267
271
|
if (extras === null)
|
|
268
272
|
return { kind: "cancelled" };
|
|
269
273
|
return { kind: "scaffold", dir, template, force, ...extras };
|
|
@@ -320,7 +324,9 @@ function commented(command, note) {
|
|
|
320
324
|
}
|
|
321
325
|
function nextSteps(outcome, fileCount, name, pm, installed = false, grant = null) {
|
|
322
326
|
const cmd = packageManagerCommands(pm);
|
|
323
|
-
|
|
327
|
+
// a chosen vendor's env var, else the one the template's own config reads (a pinned template under "none")
|
|
328
|
+
const vendorEnv = providerById(outcome.provider)?.envVar ??
|
|
329
|
+
(outcome.provider === "none" ? templateByName(outcome.template)?.provider?.envVar : undefined);
|
|
324
330
|
const startNote = grant
|
|
325
331
|
? grant.source === "trial"
|
|
326
332
|
? "25 free Nola runs — key in .env"
|
|
@@ -518,7 +524,9 @@ export async function runFlow(args, opts = {}) {
|
|
|
518
524
|
: { wrote: [], skipped: [] };
|
|
519
525
|
// An explicit editor choice is honored even when Nola itself was already set up.
|
|
520
526
|
const ide = outcome.ide === "vscode" ? await writeVscodeSetup(outcome.dir) : { wrote: [], skipped: [] };
|
|
521
|
-
const ag = outcome.agents.length > 0
|
|
527
|
+
const ag = outcome.agents.length > 0
|
|
528
|
+
? await writeAgentSkills(outcome.dir, outcome.agents)
|
|
529
|
+
: { wrote: [], removed: [], skipped: [] };
|
|
522
530
|
for (const note of [...result.skipped, ...trial.skipped, ...ide.skipped, ...ag.skipped])
|
|
523
531
|
prompter.note(note);
|
|
524
532
|
// 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";
|