nymor 1.0.1

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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +237 -0
  3. package/dist/agents/targets.js +111 -0
  4. package/dist/commands/add.js +121 -0
  5. package/dist/commands/compile.js +159 -0
  6. package/dist/commands/doctor.js +205 -0
  7. package/dist/commands/init.js +98 -0
  8. package/dist/commands/inject.js +24 -0
  9. package/dist/commands/learn.js +145 -0
  10. package/dist/commands/list.js +55 -0
  11. package/dist/commands/remove.js +38 -0
  12. package/dist/commands/update.js +80 -0
  13. package/dist/commands/validate.js +82 -0
  14. package/dist/compiler/agentsmd.js +17 -0
  15. package/dist/compiler/block.js +25 -0
  16. package/dist/compiler/claude.js +16 -0
  17. package/dist/compiler/copilot.js +29 -0
  18. package/dist/compiler/cursor.js +38 -0
  19. package/dist/compiler/kiro.js +24 -0
  20. package/dist/detector/agents.js +24 -0
  21. package/dist/detector/stack.js +113 -0
  22. package/dist/index.js +52 -0
  23. package/dist/registry/cache.js +60 -0
  24. package/dist/registry/client.js +135 -0
  25. package/dist/registry/resolver.js +29 -0
  26. package/dist/registry/types.js +2 -0
  27. package/dist/templates/bootstrap.js +97 -0
  28. package/dist/templates/cicada-json.js +11 -0
  29. package/dist/templates/nymor-json.js +11 -0
  30. package/dist/utils/manifest.js +32 -0
  31. package/dist/utils/paths.js +30 -0
  32. package/dist/utils/skills.js +114 -0
  33. package/package.json +32 -0
  34. package/src/agents/targets.ts +141 -0
  35. package/src/commands/compile.ts +202 -0
  36. package/src/commands/doctor.ts +253 -0
  37. package/src/commands/init.ts +113 -0
  38. package/src/commands/learn.ts +175 -0
  39. package/src/commands/list.ts +57 -0
  40. package/src/commands/validate.ts +89 -0
  41. package/src/compiler/block.ts +26 -0
  42. package/src/compiler/claude.ts +13 -0
  43. package/src/compiler/copilot.ts +28 -0
  44. package/src/compiler/cursor.ts +38 -0
  45. package/src/compiler/kiro.ts +22 -0
  46. package/src/detector/agents.ts +26 -0
  47. package/src/detector/stack.ts +135 -0
  48. package/src/index.ts +59 -0
  49. package/src/templates/bootstrap.ts +109 -0
  50. package/src/templates/nymor-json.ts +15 -0
  51. package/src/utils/manifest.ts +38 -0
  52. package/src/utils/paths.ts +25 -0
  53. package/src/utils/skills.ts +152 -0
  54. package/tests/compiler/__snapshots__/claude.test.ts.snap +65 -0
  55. package/tests/compiler/__snapshots__/copilot.test.ts.snap +54 -0
  56. package/tests/compiler/__snapshots__/cursor.test.ts.snap +62 -0
  57. package/tests/compiler/__snapshots__/kiro.test.ts.snap +54 -0
  58. package/tests/compiler/block.test.ts +24 -0
  59. package/tests/compiler/claude.test.ts +46 -0
  60. package/tests/compiler/copilot.test.ts +15 -0
  61. package/tests/compiler/cursor.test.ts +15 -0
  62. package/tests/compiler/kiro.test.ts +15 -0
  63. package/tests/detector/agents.test.ts +48 -0
  64. package/tests/detector/stack.test.ts +29 -0
  65. package/tests/e2e/init-and-compile.test.ts +227 -0
  66. package/tests/fixtures/skills/scoped/SKILL.md +18 -0
  67. package/tests/fixtures/skills/simple/SKILL.md +16 -0
  68. package/tests/fixtures/skills/with-examples/SKILL.md +18 -0
  69. package/tests/fixtures/skills/with-examples/examples/example.md +3 -0
  70. package/tests/fixtures/stacks/django/manage.py +2 -0
  71. package/tests/fixtures/stacks/django/requirements.txt +1 -0
  72. package/tests/fixtures/stacks/fastapi/requirements.txt +1 -0
  73. package/tests/fixtures/stacks/go/go.mod +3 -0
  74. package/tests/fixtures/stacks/nodejs/package.json +5 -0
  75. package/tests/fixtures/stacks/react/package.json +5 -0
  76. package/tests/fixtures/stacks/rust/Cargo.toml +4 -0
  77. package/tests/fixtures/stacks/vue/package.json +8 -0
  78. package/tests/fixtures/stacks/vue/vite.config.ts +5 -0
  79. package/tests/utils/manifest.test.ts +31 -0
  80. package/tests/utils/paths.test.ts +23 -0
  81. package/tests/utils/skills.test.ts +49 -0
  82. package/tsconfig.json +14 -0
  83. package/vitest.config.ts +8 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mustafakmelli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,237 @@
1
+ ```
2
+ ███╗ ██╗██╗ ██╗███╗ ███╗ ██████╗ ██████╗
3
+ ████╗ ██║╚██╗ ██╔╝████╗ ████║██╔═══██╗██╔══██╗
4
+ ██╔██╗ ██║ ╚████╔╝ ██╔████╔██║██║ ██║██████╔╝
5
+ ██║╚██╗██║ ╚██╔╝ ██║╚██╔╝██║██║ ██║██╔══██╗
6
+ ██║ ╚████║ ██║ ██║ ╚═╝ ██║╚██████╔╝██║ ██║
7
+ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
8
+ ```
9
+
10
+ # Nymor
11
+
12
+ <p align="center">
13
+ <strong>Teach your repo what your agents keep forgetting.</strong>
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="https://www.npmjs.com/package/nymor"><img src="https://img.shields.io/npm/v/nymor?color=blue&label=npm" alt="npm version" /></a>
18
+ <a href="https://github.com/mustafakmelli/nymor/blob/main/LICENSE"><img src="https://img.shields.io/github/license/mustafakmelli/nymor?color=black" alt="license" /></a>
19
+ <img src="https://img.shields.io/badge/zero-network_calls-green" alt="zero network" />
20
+ <img src="https://img.shields.io/badge/agents-10_supported-orange" alt="agents" />
21
+ </p>
22
+
23
+ ---
24
+
25
+ You correct your agent. It forgets.
26
+ You correct it again. It forgets again.
27
+ You write the rule in a comment. The next chat starts fresh.
28
+
29
+ **Nymor stops that loop.**
30
+
31
+ When a rule is worth keeping, `/nymor-learn` captures it as a skill — committed to your repo, compiled into every agent surface, loaded in every future chat. The agent stops forgetting because the memory lives in the code.
32
+
33
+ ---
34
+
35
+ ## How it feels
36
+
37
+ ```
38
+ You: "We don't use API routes here. Always use Server Actions."
39
+ Agent: "Got it. This looks like a durable repo rule —
40
+ want me to capture it with /nymor-learn?"
41
+ You: /nymor-learn "Use Server Actions for mutations, not API routes"
42
+ Agent: Writing .nymor/skills/server-actions-only/SKILL.md ...
43
+ Updating nymor.json ...
44
+ Running nymor compile ...
45
+ ✓ Skill saved and compiled to Claude, Cursor, Copilot, Kiro
46
+ ```
47
+
48
+ Next chat. Same rule. Already known.
49
+
50
+ ---
51
+
52
+ ## Install
53
+
54
+ ```sh
55
+ npm install -g nymor
56
+ ```
57
+
58
+ Or run without installing:
59
+
60
+ ```sh
61
+ npx nymor init
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Quick start
67
+
68
+ ```sh
69
+ # Initialize repo memory and agent guidance
70
+ nymor init
71
+
72
+ # Then inside your agent (Cursor, Claude, Copilot, Kiro):
73
+ /nymor-learn "Use Server Actions for mutations, not API routes"
74
+
75
+ # See what your repo has learned
76
+ nymor list
77
+
78
+ # Recompile after manual edits
79
+ nymor compile
80
+ ```
81
+
82
+ ---
83
+
84
+ ## The only workflow that matters
85
+
86
+ ```
87
+ Correct agent ──→ /nymor-learn ──→ .nymor/skills/<id>/SKILL.md
88
+
89
+ ┌───────────────────┼───────────────────┐
90
+ ↓ ↓ ↓
91
+ .claude/skills/ .cursor/rules/ .github/instructions/
92
+ CLAUDE.md nymor.mdc nymor-bootstrap.md
93
+ ↓ ↓ ↓
94
+ git commit ──────── git diff ──────── open PR
95
+ ```
96
+
97
+ The agent writes the skill. Nymor compiles it. Git owns the history. Your team reviews it like any other change.
98
+
99
+ ---
100
+
101
+ ## Supported agents
102
+
103
+ | Agent | Output |
104
+ | ------------------------------------------------------ | ---------------------------------------------- |
105
+ | Claude Code | `.claude/skills/`, `CLAUDE.md` |
106
+ | Cursor | `.cursor/rules/nymor-*.mdc` |
107
+ | GitHub Copilot | `.github/instructions/nymor-*.instructions.md`, `.github/prompts/nymor-learn.prompt.md` |
108
+ | Kiro | `.kiro/steering/nymor-*.md` |
109
+ | Codex, OpenCode, Aider, Goose, Zed, Warp, Devin, Junie | `AGENTS.md` |
110
+ | Gemini CLI | `GEMINI.md` |
111
+ | Windsurf | `.windsurf/rules/nymor.md` |
112
+ | Goose (native) | `.goose/skills/` |
113
+ | OpenCode (native) | `.opencode/skill/` |
114
+
115
+ ---
116
+
117
+ ## Skill format
118
+
119
+ Skills are plain Markdown. Human-readable, diffable, reviewable in a PR.
120
+
121
+ ```md
122
+ ---
123
+ name: Server Actions Only
124
+ description: Use this when changing application mutations.
125
+ globs:
126
+ - "app/**/*.ts"
127
+ - "app/**/*.tsx"
128
+ alwaysApply: false
129
+ ---
130
+
131
+ # Skill: Server Actions Only
132
+
133
+ ## Rule
134
+
135
+ Use Server Actions for mutations. Do not create API routes for app mutations.
136
+
137
+ ## Why
138
+
139
+ Keeps mutation logic close to the UI and preserves the repo's auth pattern.
140
+
141
+ ## Example
142
+
143
+ // WRONG
144
+ export async function POST(req: Request) { ... } // app/api/users/route.ts
145
+
146
+ // CORRECT
147
+ 'use server'
148
+ export async function updateUser(data: UserData) { ... } // app/users/actions.ts
149
+ ```
150
+
151
+ ---
152
+
153
+ ## Commands
154
+
155
+ ```sh
156
+ nymor init # initialize repo memory, write /nymor-learn to all agents
157
+ nymor compile # regenerate indexes and all agent outputs
158
+ nymor list # list active repo skills
159
+ nymor doctor # check manifest, globs, and generated outputs
160
+ nymor validate # validate skill file structure
161
+ ```
162
+
163
+ ---
164
+
165
+ ## What Nymor does not do
166
+
167
+ - **No central catalog.** Skills are yours. They live in your repo. There is nothing to install from the internet.
168
+ - **No LLM calls.** Nymor compiles and validates. The agent writes the skill. You review it.
169
+ - **No magic.** Every output is a plain file you can read, edit, delete, and commit.
170
+
171
+ ---
172
+
173
+ ## `nymor.json`
174
+
175
+ ```json
176
+ {
177
+ "version": "1",
178
+ "agents": ["claude", "cursor", "copilot", "kiro", "agents-md"],
179
+ "local": ["server-actions-only", "commit-conventions"]
180
+ }
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Generated files
186
+
187
+ ```
188
+ .nymor/
189
+ skills/
190
+ server-actions-only/
191
+ SKILL.md
192
+ index.md
193
+ index.json
194
+ nymor.json
195
+
196
+ CLAUDE.md ← Claude bootstrap
197
+ .claude/commands/nymor-learn.md ← Claude slash command
198
+ .claude/skills/<id>/SKILL.md ← Claude native skills
199
+
200
+ .cursor/rules/nymor.mdc ← Cursor bootstrap
201
+ .cursor/rules/nymor-<id>.mdc ← Cursor per-skill
202
+ .cursor/commands/nymor-learn.md ← Cursor slash command
203
+
204
+ .github/instructions/nymor-bootstrap.instructions.md
205
+ .github/instructions/nymor-<id>.instructions.md
206
+ .github/prompts/nymor-learn.prompt.md ← Copilot slash command
207
+
208
+ .kiro/steering/nymor.md
209
+ .kiro/steering/nymor-<id>.md
210
+
211
+ AGENTS.md ← Codex, Aider, Goose, etc.
212
+ GEMINI.md
213
+ .windsurf/rules/nymor.md
214
+ .goose/skills/<id>/SKILL.md
215
+ .opencode/skill/<id>/SKILL.md
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Development
221
+
222
+ ```sh
223
+ npm run build
224
+ npm test
225
+ ```
226
+
227
+ ---
228
+
229
+ ## Philosophy
230
+
231
+ > The agent is smart. Your repo is the memory.
232
+
233
+ Skills are not configuration. They are the accumulated knowledge of how your team works — decisions made, mistakes corrected, patterns established. Nymor makes that knowledge durable, reviewable, and portable across every agent your team uses.
234
+
235
+ Every `/nymor-learn` is a conversation turned into institutional memory.
236
+
237
+ Made for teams who want their agents to actually know how they work.
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_AGENT_TARGETS = exports.AGENT_TARGETS = void 0;
7
+ exports.getAgentTargetDefinition = getAgentTargetDefinition;
8
+ exports.isAgentTarget = isAgentTarget;
9
+ const path_1 = __importDefault(require("path"));
10
+ exports.AGENT_TARGETS = [
11
+ {
12
+ id: "claude",
13
+ label: "Claude Code",
14
+ short: "Claude",
15
+ description: "Claude skills, CLAUDE.md bootstrap, and /nymor-learn command",
16
+ detectPaths: [".claude", "CLAUDE.md"],
17
+ kind: "claude",
18
+ bootstrapFile: "CLAUDE.md",
19
+ commandFile: path_1.default.join(".claude", "commands", "nymor-learn.md")
20
+ },
21
+ {
22
+ id: "cursor",
23
+ label: "Cursor",
24
+ short: "Cursor",
25
+ description: "Cursor rules and /nymor-learn command",
26
+ detectPaths: [".cursor"],
27
+ kind: "cursor",
28
+ bootstrapFile: path_1.default.join(".cursor", "rules", "nymor.mdc"),
29
+ commandFile: path_1.default.join(".cursor", "commands", "nymor-learn.md")
30
+ },
31
+ {
32
+ id: "copilot",
33
+ label: "GitHub Copilot",
34
+ short: "Copilot",
35
+ description: "GitHub Copilot instructions and /nymor-learn prompt",
36
+ detectPaths: [
37
+ path_1.default.join(".github", "copilot-instructions.md"),
38
+ path_1.default.join(".github", "instructions"),
39
+ path_1.default.join(".github", "prompts")
40
+ ],
41
+ kind: "copilot",
42
+ bootstrapFile: path_1.default.join(".github", "instructions", "nymor-bootstrap.instructions.md"),
43
+ commandFile: path_1.default.join(".github", "prompts", "nymor-learn.prompt.md")
44
+ },
45
+ {
46
+ id: "kiro",
47
+ label: "Kiro",
48
+ short: "Kiro",
49
+ description: "Kiro steering files",
50
+ detectPaths: [".kiro"],
51
+ kind: "kiro",
52
+ bootstrapFile: path_1.default.join(".kiro", "steering", "nymor.md")
53
+ },
54
+ {
55
+ id: "agents-md",
56
+ label: "AGENTS.md",
57
+ short: "AGENTS.md",
58
+ description: "Shared AGENTS.md for Codex, OpenCode, Aider, Goose, Zed, Warp, Devin, and Junie",
59
+ detectPaths: ["AGENTS.md", ".codex", ".aider.conf.yml", ".zed", ".warp", ".junie", ".goose", ".opencode"],
60
+ kind: "shared-md",
61
+ bootstrapFile: "AGENTS.md",
62
+ sharedConsumers: ["Codex", "OpenCode", "Aider", "Goose", "Zed", "Warp", "Devin", "Junie"]
63
+ },
64
+ {
65
+ id: "gemini",
66
+ label: "Gemini CLI",
67
+ short: "Gemini",
68
+ description: "GEMINI.md managed block",
69
+ detectPaths: ["GEMINI.md", ".gemini"],
70
+ kind: "gemini",
71
+ bootstrapFile: "GEMINI.md"
72
+ },
73
+ {
74
+ id: "windsurf",
75
+ label: "Windsurf",
76
+ short: "Windsurf",
77
+ description: "Windsurf project rule file",
78
+ detectPaths: [".windsurf", ".windsurfrules"],
79
+ kind: "windsurf",
80
+ bootstrapFile: path_1.default.join(".windsurf", "rules", "nymor.md")
81
+ },
82
+ {
83
+ id: "goose",
84
+ label: "Goose",
85
+ short: "Goose",
86
+ description: "Goose native skills",
87
+ detectPaths: [".goose"],
88
+ kind: "native-skills",
89
+ nativeSkillDir: path_1.default.join(".goose", "skills")
90
+ },
91
+ {
92
+ id: "opencode",
93
+ label: "OpenCode",
94
+ short: "OpenCode",
95
+ description: "OpenCode native skills",
96
+ detectPaths: [".opencode", "opencode.json"],
97
+ kind: "native-skills",
98
+ nativeSkillDir: path_1.default.join(".opencode", "skill")
99
+ }
100
+ ];
101
+ exports.DEFAULT_AGENT_TARGETS = exports.AGENT_TARGETS.map((target) => target.id);
102
+ function getAgentTargetDefinition(id) {
103
+ const target = exports.AGENT_TARGETS.find((item) => item.id === id);
104
+ if (!target) {
105
+ throw new Error(`Unknown agent target: ${id}`);
106
+ }
107
+ return target;
108
+ }
109
+ function isAgentTarget(value) {
110
+ return exports.AGENT_TARGETS.some((target) => target.id === value);
111
+ }
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.addCommand = addCommand;
7
+ exports.installRegistrySkill = installRegistrySkill;
8
+ exports.parseRegistrySkill = parseRegistrySkill;
9
+ exports.createEmptyLockfile = createEmptyLockfile;
10
+ exports.computeTarballIntegrity = computeTarballIntegrity;
11
+ const crypto_1 = __importDefault(require("crypto"));
12
+ const fs_extra_1 = __importDefault(require("fs-extra"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const picocolors_1 = __importDefault(require("picocolors"));
15
+ const compile_1 = require("./compile");
16
+ const client_1 = require("../registry/client");
17
+ const cache_1 = require("../registry/cache");
18
+ const resolver_1 = require("../registry/resolver");
19
+ const manifest_1 = require("../utils/manifest");
20
+ const paths_1 = require("../utils/paths");
21
+ const INDEX_TTL_MS = 60 * 60 * 1000;
22
+ async function addCommand(skill, options = {}) {
23
+ const projectRoot = process.cwd();
24
+ const parsed = parseRegistrySkill(skill);
25
+ const manifest = await (0, manifest_1.readManifest)(projectRoot);
26
+ if (manifest.skills[parsed.packageName]) {
27
+ console.log(picocolors_1.default.yellow(`Skill ${parsed.packageName} is already in cicada.json; reinstalling.`));
28
+ }
29
+ const requestedRange = options.version ?? "latest";
30
+ const result = await installRegistrySkill(projectRoot, parsed, requestedRange, Boolean(options.offline));
31
+ manifest.skills[parsed.packageName] = requestedRange;
32
+ await (0, manifest_1.writeManifest)(projectRoot, manifest);
33
+ const lockfile = (await (0, manifest_1.readLockfile)(projectRoot)) ?? createEmptyLockfile();
34
+ lockfile.skills[parsed.packageName] = {
35
+ version: result.version,
36
+ integrity: result.integrity,
37
+ resolved: result.resolved
38
+ };
39
+ await (0, manifest_1.writeLockfile)(projectRoot, lockfile);
40
+ await (0, compile_1.compileCommand)();
41
+ console.log("");
42
+ console.log(`${picocolors_1.default.green("✓")} Installed ${result.packageName}@${result.version}`);
43
+ console.log(` ${result.destinationDir}`);
44
+ }
45
+ async function installRegistrySkill(projectRoot, parsed, range, offline) {
46
+ const index = await getSkillIndex(parsed, offline);
47
+ const version = (0, resolver_1.resolveVersion)(range, Object.keys(index.versions));
48
+ const registryVersion = index.versions[version];
49
+ if (!registryVersion) {
50
+ throw new Error(`Registry index for ${parsed.packageName} does not include ${version}`);
51
+ }
52
+ const { tarball, integrity } = await getTarball(parsed, version, registryVersion.integrity, offline);
53
+ const destinationDir = path_1.default.join((0, paths_1.getSkillsDir)(projectRoot), parsed.folderName);
54
+ await fs_extra_1.default.emptyDir(destinationDir);
55
+ await (0, client_1.extractSkillTarball)(tarball, destinationDir);
56
+ const skillPath = path_1.default.join(destinationDir, "SKILL.md");
57
+ if (!(await fs_extra_1.default.pathExists(skillPath))) {
58
+ throw new Error(`Installed tarball for ${parsed.packageName}@${version} did not contain SKILL.md`);
59
+ }
60
+ return {
61
+ packageName: parsed.packageName,
62
+ version,
63
+ integrity,
64
+ resolved: (0, client_1.getSkillTarballUrl)(parsed.scope, parsed.name, version),
65
+ destinationDir
66
+ };
67
+ }
68
+ function parseRegistrySkill(value) {
69
+ const match = /^(@[^/\s]+)\/([^/\s]+)$/.exec(value);
70
+ if (!match) {
71
+ throw new Error(`Invalid skill "${value}". Expected a scoped package like @cicada/commit-conventions.`);
72
+ }
73
+ const [, scope, name] = match;
74
+ return {
75
+ packageName: `${scope}/${name}`,
76
+ scope,
77
+ name,
78
+ folderName: `${scope}__${name}`
79
+ };
80
+ }
81
+ function createEmptyLockfile() {
82
+ return {
83
+ lockfileVersion: 1,
84
+ skills: {}
85
+ };
86
+ }
87
+ function computeTarballIntegrity(tarball) {
88
+ return `sha256-${crypto_1.default.createHash("sha256").update(tarball).digest("base64")}`;
89
+ }
90
+ async function getSkillIndex(parsed, offline) {
91
+ const cacheKey = parsed.folderName;
92
+ const cached = await (0, cache_1.getCachedIndex)(cacheKey, offline ? Number.POSITIVE_INFINITY : INDEX_TTL_MS);
93
+ if (cached) {
94
+ return cached;
95
+ }
96
+ if (offline) {
97
+ throw new Error(`No cached registry index for ${parsed.packageName}; cannot add in offline mode.`);
98
+ }
99
+ const index = await (0, client_1.fetchSkillIndex)(parsed.scope, parsed.name);
100
+ await (0, cache_1.putCachedIndex)(cacheKey, index);
101
+ return index;
102
+ }
103
+ async function getTarball(parsed, version, expectedIntegrity, offline) {
104
+ const cached = await (0, cache_1.getCachedTarball)(parsed.scope, parsed.name, version);
105
+ if (cached) {
106
+ const cachedIntegrity = computeTarballIntegrity(cached);
107
+ if (cachedIntegrity !== expectedIntegrity) {
108
+ throw new Error(`Cached tarball integrity mismatch for ${parsed.packageName}@${version}: expected ${expectedIntegrity}, got ${cachedIntegrity}`);
109
+ }
110
+ return { tarball: cached, integrity: cachedIntegrity };
111
+ }
112
+ if (offline) {
113
+ throw new Error(`No cached tarball for ${parsed.packageName}@${version}; cannot add in offline mode.`);
114
+ }
115
+ const fetched = await (0, client_1.fetchSkillTarball)(parsed.scope, parsed.name, version);
116
+ if (fetched.integrity !== expectedIntegrity) {
117
+ throw new Error(`Registry integrity mismatch for ${parsed.packageName}@${version}: index has ${expectedIntegrity}, integrity.txt has ${fetched.integrity}`);
118
+ }
119
+ await (0, cache_1.putCachedTarball)(parsed.scope, parsed.name, version, fetched.tarball, fetched.integrity);
120
+ return fetched;
121
+ }
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.compileCommand = compileCommand;
7
+ exports.planCompileOutputs = planCompileOutputs;
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const targets_1 = require("../agents/targets");
11
+ const copilot_1 = require("../compiler/copilot");
12
+ const cursor_1 = require("../compiler/cursor");
13
+ const kiro_1 = require("../compiler/kiro");
14
+ const block_1 = require("../compiler/block");
15
+ const bootstrap_1 = require("../templates/bootstrap");
16
+ const skills_1 = require("../utils/skills");
17
+ const manifest_1 = require("../utils/manifest");
18
+ const paths_1 = require("../utils/paths");
19
+ async function compileCommand() {
20
+ const projectRoot = process.cwd();
21
+ const skillsDir = (0, paths_1.getSkillsDir)(projectRoot);
22
+ if (!(await fs_extra_1.default.pathExists(skillsDir))) {
23
+ console.log("No skills found. Run nymor init first.");
24
+ process.exitCode = 1;
25
+ return;
26
+ }
27
+ const skills = await (0, skills_1.loadSkills)(skillsDir);
28
+ const { markdown, json } = (0, skills_1.buildSkillIndex)(skills);
29
+ await fs_extra_1.default.ensureDir((0, paths_1.getNymorDir)(projectRoot));
30
+ await fs_extra_1.default.writeFile((0, paths_1.getIndexMarkdownPath)(projectRoot), markdown, "utf8");
31
+ await fs_extra_1.default.writeFile((0, paths_1.getIndexJsonPath)(projectRoot), json, "utf8");
32
+ const manifest = await (0, manifest_1.readManifest)(projectRoot);
33
+ const agentSet = new Set(manifest.agents);
34
+ for (const target of targets_1.AGENT_TARGETS) {
35
+ if (agentSet.has(target.id)) {
36
+ await writeTargetOutputs(projectRoot, target, skills);
37
+ }
38
+ }
39
+ console.log(`Compiled ${skills.length} skills.`);
40
+ }
41
+ async function planCompileOutputs(projectRoot) {
42
+ const skillsDir = (0, paths_1.getSkillsDir)(projectRoot);
43
+ const skills = await (0, skills_1.loadSkills)(skillsDir);
44
+ const { markdown, json } = (0, skills_1.buildSkillIndex)(skills);
45
+ const manifest = await (0, manifest_1.readManifest)(projectRoot);
46
+ const agentSet = new Set(manifest.agents);
47
+ const files = [
48
+ textFile((0, paths_1.getIndexMarkdownPath)(projectRoot), markdown),
49
+ textFile((0, paths_1.getIndexJsonPath)(projectRoot), json)
50
+ ];
51
+ for (const target of targets_1.AGENT_TARGETS) {
52
+ if (agentSet.has(target.id)) {
53
+ files.push(...(await planTargetOutputs(projectRoot, target, skills)));
54
+ }
55
+ }
56
+ return files;
57
+ }
58
+ async function writeTargetOutputs(projectRoot, target, skills) {
59
+ for (const file of await planTargetOutputs(projectRoot, target, skills)) {
60
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(file.path));
61
+ await fs_extra_1.default.writeFile(file.path, file.content);
62
+ }
63
+ }
64
+ async function planTargetOutputs(projectRoot, target, skills) {
65
+ const files = [];
66
+ if (target.bootstrapFile) {
67
+ files.push(await planBootstrap(projectRoot, target, skills));
68
+ }
69
+ if (target.commandFile) {
70
+ files.push(textFile(path_1.default.join(projectRoot, target.commandFile), `${(0, bootstrap_1.renderLearnCommand)(target)}\n`));
71
+ }
72
+ switch (target.kind) {
73
+ case "claude":
74
+ files.push(...(await planClaudeOutputs(skills, projectRoot)));
75
+ break;
76
+ case "cursor":
77
+ for (const skill of skills) {
78
+ files.push(textFile(path_1.default.join(projectRoot, ".cursor", "rules", `nymor-${skill.id}.mdc`), (0, cursor_1.renderCursorRule)(skill)));
79
+ }
80
+ break;
81
+ case "copilot":
82
+ for (const skill of skills) {
83
+ files.push(textFile(path_1.default.join(projectRoot, ".github", "instructions", `nymor-${skill.id}.instructions.md`), (0, copilot_1.renderCopilotInstructions)(skill)));
84
+ }
85
+ break;
86
+ case "kiro":
87
+ for (const skill of skills) {
88
+ files.push(textFile(path_1.default.join(projectRoot, ".kiro", "steering", `nymor-${skill.id}.md`), (0, kiro_1.renderKiroSteering)(skill)));
89
+ }
90
+ break;
91
+ case "native-skills":
92
+ if (target.nativeSkillDir) {
93
+ files.push(...(await planNativeSkillOutputs(projectRoot, target, skills)));
94
+ }
95
+ break;
96
+ case "shared-md":
97
+ case "gemini":
98
+ case "windsurf":
99
+ break;
100
+ }
101
+ return files;
102
+ }
103
+ async function planBootstrap(projectRoot, target, skills) {
104
+ const targetPath = path_1.default.join(projectRoot, target.bootstrapFile);
105
+ const content = (0, bootstrap_1.renderBootstrap)(target, skills);
106
+ if (target.id === "claude" || target.id === "agents-md" || target.id === "gemini") {
107
+ const existing = (await fs_extra_1.default.pathExists(targetPath)) ? await fs_extra_1.default.readFile(targetPath, "utf8") : null;
108
+ return textFile(targetPath, (0, block_1.upsertManagedBlock)(existing, content));
109
+ }
110
+ return textFile(targetPath, `${content.trimEnd()}\n`);
111
+ }
112
+ async function planClaudeOutputs(skills, projectRoot) {
113
+ const outputRoot = path_1.default.join(projectRoot, ".claude", "skills");
114
+ const files = [];
115
+ for (const skill of skills) {
116
+ const sourceFiles = await listFilesRecursive(skill.dirPath);
117
+ for (const sourcePath of sourceFiles) {
118
+ const relative = path_1.default.relative(skill.dirPath, sourcePath);
119
+ files.push({
120
+ path: path_1.default.join(outputRoot, skill.id, relative),
121
+ content: await fs_extra_1.default.readFile(sourcePath)
122
+ });
123
+ }
124
+ }
125
+ return files;
126
+ }
127
+ async function planNativeSkillOutputs(projectRoot, target, skills) {
128
+ const files = [
129
+ textFile(path_1.default.join(projectRoot, target.nativeSkillDir, "nymor-learn", "SKILL.md"), `${(0, bootstrap_1.renderLearnSkill)(target)}\n`)
130
+ ];
131
+ for (const skill of skills) {
132
+ const sourceFiles = await listFilesRecursive(skill.dirPath);
133
+ for (const sourcePath of sourceFiles) {
134
+ const relative = path_1.default.relative(skill.dirPath, sourcePath);
135
+ files.push({
136
+ path: path_1.default.join(projectRoot, target.nativeSkillDir, skill.id, relative),
137
+ content: await fs_extra_1.default.readFile(sourcePath)
138
+ });
139
+ }
140
+ }
141
+ return files;
142
+ }
143
+ async function listFilesRecursive(root) {
144
+ const entries = await fs_extra_1.default.readdir(root, { withFileTypes: true });
145
+ const files = [];
146
+ for (const entry of entries) {
147
+ const entryPath = path_1.default.join(root, entry.name);
148
+ if (entry.isDirectory()) {
149
+ files.push(...(await listFilesRecursive(entryPath)));
150
+ }
151
+ else if (entry.isFile()) {
152
+ files.push(entryPath);
153
+ }
154
+ }
155
+ return files.sort();
156
+ }
157
+ function textFile(filePath, content) {
158
+ return { path: filePath, content: Buffer.from(content, "utf8") };
159
+ }