wyvrnpm 2.20.2 → 2.21.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.
package/README.md CHANGED
@@ -4,7 +4,21 @@ A simple, private C++ package manager that works with any static file hosting pr
4
4
 
5
5
  There is no central registry. You control where packages are hosted.
6
6
 
7
- > **README version: 2.15.0**
7
+ > **README version: 2.20.2**
8
+ >
9
+ > **New in 2.16.0 – 2.20.2:**
10
+ >
11
+ > - **Visual Studio architecture pinning (2.20.0).** A recipe-less Windows/MSVC consumer (one that only lists `dependencies`, no `build` recipe) used to get a generator-less `wyvrn-<profile>` preset, so `cmake --preset` fell through to CMake's implicit Visual Studio default — which targets the **host** arch (x64) when no `-A` is supplied. An `x86` / `armv8` profile therefore silently produced 64-bit binaries. `wyvrnpm install` now detects the default VS generator and bakes both `generator` and `architecture` into the emitted preset, and `wyvrnpm build` derives `-A` (`x86→Win32`, `x86_64→x64`, `armv8→ARM64`, `armv7→ARM`) for VS generators. Builds now match the profile's bitness without a recipe. (Already-published non-host-arch artefacts built via the old source-build path may still hold wrong-bitness binaries — those are rebuilt + republished out of band.)
12
+ > - **`wyvrnpm build` resolves the generator from the emitted preset, not the recipe (2.20.2).** The configure step now reads the generator that `cmake --preset` will *actually* use — precedence: CLI `--generator` > the `generator` baked into `CMakePresets.json` > the detected default for a generator-less preset. This drives both the `-A` pin and the vcvarsall-skip decision, so they stay consistent with reality. Fixes a class of failures where a Ninja Multi-Config preset got `-A x64` ("does not support platform specification") and skipped vcvarsall ("CMAKE_CXX_COMPILER not set") because the old code re-derived the generator from the recipe — which can legitimately diverge from the preset (an install-time `--generator`, or a recipe edited after the last install).
13
+ > - **CMake-path fallback (2.20.1).** `install` / `build` / source-build no longer fail outright on a machine where CMake isn't on `PATH` — `resolveCmakeExe()` falls back to the Windows default `C:\Program Files\CMake\bin\cmake.exe` before surfacing the usual "is CMake installed?" error.
14
+ > - **Machine-wide binary package cache (2.19.0).** Every successful v2 exact-match download is also cached under `%LOCALAPPDATA%\wyvrnpm\pkg\` (14-day TTL) so re-installs across projects skip the network. `wyvrnpm cache list` / `cache prune` gain a `--type src|pkg|all` selector covering both this and the source-build cache, and `wyvrnpm clean --pkg-cache` wipes it.
15
+ > - **`wyvrnpm test` (2.18.0).** A `ctest --preset wyvrn-<profile>-<config>` wrapper that auto-builds when stale, supports `-R` / `-L` filters and `--all-configs`, and emits a `--format json` summary for CI.
16
+ > - **Output directory restructure (2.17.0).** Compiled artefacts now land inside the CMake binary dir (`<binaryDir>/lib`, `<binaryDir>/bin`) instead of `${CMAKE_SOURCE_DIR}/output/`, so `wyvrnpm clean --all` actually catches them. Scripts that hard-coded `output/bin64/…` must switch paths — `clean --all` sweeps the legacy `output/` tree as a migration aid.
17
+ > - **`wyvrnpm clean` flags (2.16.0).** `--build-dir` (also wipe the active profile's CMake build tree), `--all` (scorched-earth: build dirs + `wyvrn-*` presets + legacy `output/`), and `--dry-run` (preview, write nothing).
18
+ >
19
+ > **Planned (not yet shipped):**
20
+ >
21
+ > - **Resolver → provider unification.** Today the dependency resolver (`resolve.js` / `download.js`) talks to registries through a bare `fetch` client that hard-codes the v2/v1 URL layouts, separate from the provider classes that handle publish + download. The plan is to route all metadata lookups through provider methods (`v2GetVersionsIndex` / `v2GetMeta` / `v2GetLatest` / `v1*`) so S3 SigV4, auth resolution, and provider-specific quirks live in one place. Design captured in [.claude-wyvrn-local/plans/2026-04-24-resolver-http-unify.md](.claude-wyvrn-local/plans/2026-04-24-resolver-http-unify.md). The 2.8.4 `Accept-Encoding: identity` hotfix covers the immediate CloudFront cache-split symptom in the meantime.
8
22
  >
9
23
  > **New in 2.15.0:**
10
24
  >
@@ -371,7 +385,7 @@ After download, `install` also generates:
371
385
  - `wyvrn_internal/wyvrn_toolchain.cmake` — CMake toolchain pointing at the resolved packages.
372
386
  - `wyvrn_internal/wyvrn_deps.cmake` — post-`project()` include for `find_package()` wiring and runtime DLL copy.
373
387
  - `wyvrn_internal/wyvrn_profile.json` — snapshot of the active build profile.
374
- - `CMakePresets.json` (or `CMakeUserPresets.json` if the project root file is user-owned) with a `wyvrn-<profile>` configure preset. Presets for other profiles are preserved so multiple profiles coexist.
388
+ - `CMakePresets.json` (or `CMakeUserPresets.json` if the project root file is user-owned) with a `wyvrn-<profile>` configure preset. Presets for other profiles are preserved so multiple profiles coexist. On Windows/MSVC the preset is pinned to a concrete Visual Studio `generator` **and** `architecture` (derived from the profile arch) even when the project carries no `build` recipe — so a recipe-less `x86` / `armv8` consumer builds the correct bitness instead of CMake's implicit host-x64 default (2.20.0). Declare a `build.generator` (or a `CMakeUserPresets.json`) to opt into Ninja/Make instead.
375
389
 
376
390
  **Toolchain variables exposed to your `CMakeLists.txt` (valid BEFORE `project()`)**:
377
391
 
@@ -471,6 +485,8 @@ wyvrnpm build -- -j 8
471
485
 
472
486
  Everything after `--` is forwarded verbatim to `cmake --build` (e.g. `-j 8`).
473
487
 
488
+ **How the effective generator is resolved (2.20.2+).** `wyvrnpm build` decides the `-A` arch pin and the vcvarsall-skip from the generator `cmake --preset` will *actually* use — not the project's `build` recipe. Precedence: CLI `--generator` (wins, passed as `-G`) → the `generator` baked into `CMakePresets.json` at install time → the detected default Visual Studio generator for a generator-less preset. Trusting the preset matters because it can legitimately diverge from the recipe (an install-time `--generator`, or a recipe edited after the last install); pinning `-A` against a non-VS preset is a hard CMake error, so the two decisions must agree with what cmake runs.
489
+
474
490
  ---
475
491
 
476
492
  ### `wyvrnpm test` (2.18.0+)
@@ -620,16 +636,32 @@ Removes the `wyvrn_internal/` package cache and `wyvrn.lock`.
620
636
  # Project-local cleanup
621
637
  wyvrnpm clean
622
638
 
639
+ # Also wipe the active profile's CMake build tree
640
+ wyvrnpm clean --build-dir
641
+
642
+ # Scorched earth: build dirs + wyvrn-* presets + legacy output/ tree
643
+ wyvrnpm clean --all
644
+
623
645
  # Also wipe the machine-wide source-build cache used by --build=missing
624
646
  wyvrnpm clean --build-cache
647
+
648
+ # Also wipe the machine-wide binary package cache
649
+ wyvrnpm clean --pkg-cache
650
+
651
+ # Preview every action without deleting anything
652
+ wyvrnpm clean --all --dry-run
625
653
  ```
626
654
 
627
655
  **Options**
628
656
 
629
657
  | Option | Default | Description |
630
658
  |---|---|---|
659
+ | `--build-dir` | `false` | Also remove the active profile's CMake build tree at `<binaryDirRoot>/wyvrn-<profile>/`. |
660
+ | `--all` | `false` | Default scrub **plus** every `<binaryDirRoot>/wyvrn-*/` build dir, the `wyvrn-*` entries stripped from `CMakePresets.json`, and the legacy `<root>/output/` tree (2.17.0 migration aid). |
631
661
  | `--build-cache` | `false` | Also remove the source-build cache under `%LOCALAPPDATA%\wyvrnpm\bc\` (Unix: `~/.cache/wyvrnpm/bc/`). |
632
- | `--uploaded-built` | `false` | Wipe only the local record of what this machine has uploaded via `--upload-built` (the `.uploaded-to.json` sidecars). Does **not** touch the registry, the artefacts, or `wyvrn_internal/`. |
662
+ | `--pkg-cache` | `false` | Also remove the machine-wide binary package cache under `%LOCALAPPDATA%\wyvrnpm\pkg\` (2.19.0). Opt-in only never wiped by the default scrub or `--all`. |
663
+ | `--uploaded-built` | `false` | Wipe only the local record of what this machine has uploaded via `--upload-built` (the `.uploaded-to.json` sidecars). Does **not** touch the registry, the artefacts, or `wyvrn_internal/`. When passed alone, the default scrub is skipped. |
664
+ | `--dry-run` | `false` | Log `Would remove …` for every action and write nothing. |
633
665
 
634
666
  For selective cache pruning (keep last N variants, drop entries older than N days), use `wyvrnpm cache prune` instead — see below.
635
667
 
@@ -637,12 +669,22 @@ For selective cache pruning (keep last N variants, drop entries older than N day
637
669
 
638
670
  ### `wyvrnpm cache` (2.8.0+)
639
671
 
640
- Inspect and selectively prune the machine-wide source-build cache at `%LOCALAPPDATA%\wyvrnpm\bc\` (Unix: `~/.cache/wyvrnpm/bc/`). Useful once `--upload-built` adoption balloons cache size on dev rigs — `clean --build-cache` is the blunt alternative that wipes everything.
672
+ Inspect and selectively prune the two machine-wide caches. Useful once cache size balloons on dev rigs — `clean --build-cache` / `clean --pkg-cache` are the blunt alternatives that wipe everything of one type.
673
+
674
+ | Type | Location | Populated by | TTL |
675
+ |---|---|---|---|
676
+ | `src` | `%LOCALAPPDATA%\wyvrnpm\bc\` (Unix: `~/.cache/wyvrnpm/bc/`) | source-builds (`--build=missing`) | none |
677
+ | `pkg` | `%LOCALAPPDATA%\wyvrnpm\pkg\` | every successful v2 exact-match download (2.19.0+) | 14 days |
678
+
679
+ `--type src|pkg|all` (default `all`) restricts both `list` and `prune` to one cache.
641
680
 
642
681
  ```bash
643
- # List every cached entry as a table
682
+ # List every cached entry across both caches as a table
644
683
  wyvrnpm cache list
645
684
 
685
+ # Only the binary package cache
686
+ wyvrnpm cache list --type pkg
687
+
646
688
  # Filter by package name (glob)
647
689
  wyvrnpm cache list "boost*"
648
690
 
@@ -652,8 +694,8 @@ wyvrnpm cache list --format json
652
694
  # Keep the 2 most-recently-touched variants per (name, version)
653
695
  wyvrnpm cache prune --keep-last 2
654
696
 
655
- # Drop entries older than 30 days
656
- wyvrnpm cache prune --older-than 30d
697
+ # Prune only the source-build cache, dropping entries older than 30 days
698
+ wyvrnpm cache prune --type src --older-than 30d
657
699
 
658
700
  # Combine: keep 1 recent per version AND drop anything stale
659
701
  wyvrnpm cache prune --keep-last 1 --older-than 30d
@@ -667,12 +709,14 @@ wyvrnpm cache prune --keep-last 2 --dry-run
667
709
  | Option | Default | Description |
668
710
  |---|---|---|
669
711
  | `[pattern]` / `--pattern` | *(none)* | Glob over package names (`*` for any chars, `?` for one). |
712
+ | `--type` | `all` | Which cache to inspect: `src` (source-build), `pkg` (binary), or `all`. |
670
713
  | `--format` | `text` | `text` (table) or `json` (structured payload for CI). |
671
714
 
672
715
  **`cache prune` options**
673
716
 
674
717
  | Option | Default | Description |
675
718
  |---|---|---|
719
+ | `--type` | `all` | Which cache to prune: `src` (source-build), `pkg` (binary), or `all`. |
676
720
  | `--keep-last <N>` | *(none)* | Retain the N most-recently-touched entries **per (name, version) group**. A four-profile rig keeping-last-2 still retains the two newest profile variants of each version. |
677
721
  | `--older-than <dur>` | *(none)* | Remove entries whose mtime is older than the threshold. Format: `<number><unit>`, unit ∈ `s` / `m` / `h` / `d` (e.g. `30d`, `72h`). |
678
722
  | `--pattern <glob>` | *(none)* | Restrict scope to package names matching the glob. |
@@ -729,10 +773,13 @@ Useful for: auditing consumer uploads (who pushed this artefact, from which comm
729
773
 
730
774
  ### `wyvrnpm install-skill`
731
775
 
732
- Installs the bundled wyvrnpm Agent Skill — the authoring guide Claude Code uses when it sees a `wyvrn.json` in the workspace. Ships as a `.skill` zip inside the npm package (`claude/skills/wyvrnpm.skill`) and is extracted into `~/.claude/skills/wyvrnpm/` on demand.
776
+ Installs the bundled Claude Code assets that ship inside the npm package:
777
+
778
+ - The **wyvrnpm Agent Skill** — the authoring guide Claude uses when it sees a `wyvrn.json` in the workspace. Ships as a `.skill` zip (`claude/skills/wyvrnpm.skill`), extracted into `~/.claude/skills/wyvrnpm/`.
779
+ - The **onboarding agent(s)** — e.g. `wyvrnpm-onboarding`, which sets up a project to consume wyvrnpm deps or converts an existing CMake project into a wyvrnpm package. Ships as Markdown (`claude/agents/*.md`), copied into `~/.claude/agents/`.
733
780
 
734
781
  ```bash
735
- # One-shot: install the bundled skill for Claude Code
782
+ # One-shot: install the bundled skill + agent(s) for Claude Code
736
783
  wyvrnpm install-skill --claude
737
784
 
738
785
  # Overwrite an existing installation (e.g. after upgrading wyvrnpm)
@@ -742,7 +789,7 @@ wyvrnpm install-skill --claude --force
742
789
  wyvrnpm install-skill --claude --dry-run
743
790
  ```
744
791
 
745
- You only need this if you want Claude Code to pick up the skill. The CLI itself works without it.
792
+ You only need this if you want Claude Code to pick up the skill + agent. The CLI itself works without it.
746
793
 
747
794
  **Options**
748
795
 
package/bin/wyvrnpm.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  'use strict';
4
4
 
@@ -1098,8 +1098,9 @@ yargs
1098
1098
  y
1099
1099
  .positional('name', { type: 'string', description: 'Package name to link' })
1100
1100
  .positional('path', { type: 'string', description: 'Local path to package' })
1101
- .option('list', { type: 'boolean', description: 'List all globally registered packages', default: false })
1102
- .option('subdir', { type: 'string', description: 'Subdirectory within the package to link' });
1101
+ .option('list', { type: 'boolean', description: 'List all globally registered packages', default: false })
1102
+ .option('subdir', { type: 'string', description: 'Subdirectory within the package to link' })
1103
+ .option('profile', { type: 'string', description: 'Profile whose install tree to register (default: config default)' });
1103
1104
  },
1104
1105
  (argv) => link(argv),
1105
1106
  )
@@ -1126,7 +1127,7 @@ yargs
1126
1127
  // ── install-skill ─────────────────────────────────────────────────────────
1127
1128
  .command(
1128
1129
  'install-skill',
1129
- 'Install the bundled wyvrnpm Agent Skill into Claude Code (~/.claude/skills/wyvrnpm/)',
1130
+ 'Install the bundled wyvrnpm Agent Skill + onboarding agent(s) into Claude Code (~/.claude/skills/ + ~/.claude/agents/)',
1130
1131
  (y) => {
1131
1132
  y
1132
1133
  .option('claude', { type: 'boolean', description: 'Install for Claude Code', default: false })
@@ -0,0 +1,159 @@
1
+ # wyvrnpm Agents
2
+
3
+ Claude Code **subagents** that ship with wyvrnpm. A subagent is a separate
4
+ Claude worker with its own context window, tool access, and a clean return
5
+ boundary — you delegate a self-contained task to it and get back a result,
6
+ without the back-and-forth cluttering your main conversation.
7
+
8
+ These are distributed the same way as the wyvrnpm **skill**: the source lives
9
+ here under `claude/agents/`, and an active copy is placed in your user-global
10
+ `~/.claude/agents/` so Claude Code picks it up in every project.
11
+
12
+ > Skill vs. agent: the **skill** (`claude/skills/wyvrnpm/`) is *knowledge* — it
13
+ > teaches Claude how wyvrnpm works. An **agent** is an *execution context* that
14
+ > does a job, loading the skill for reference. The onboarding agent below uses
15
+ > the skill; it does not replace it.
16
+
17
+ ---
18
+
19
+ ## `wyvrnpm-onboarding`
20
+
21
+ ### What it is
22
+
23
+ A guided agent that gets a C++ developer who is **new to wyvrnpm** productive in
24
+ one shot. It handles two distinct goals and figures out which one you want:
25
+
26
+ | Mode | Goal | What it does |
27
+ |---|---|---|
28
+ | **A — Consume** | "I want to *use* libraries from our team registry in this project." | Configures the install source + profile, writes `wyvrn.json`, runs `wyvrnpm install`, wires your `CMakeLists.txt` (`wyvrnpm_find_dependencies()`, `wyvrn::<dep>` links, runtime-DLL copy), and confirms the build configures. |
29
+ | **B — Convert / publish** | "I want to turn *this* library into a wyvrnpm package others can depend on." | Reads your `CMakeLists.txt`, picks `kind` / `options` / a `build` recipe, writes `wyvrn.json` + `.wyvrnignore`, then **verifies** by running `wyvrnpm install` and `wyvrnpm build --install --all-configs` across all four configs. Hands you a ready-to-run `publish` command — it never publishes itself. |
30
+
31
+ ### Why it exists
32
+
33
+ wyvrnpm has no central registry and a few first-day gotchas (4-part versions,
34
+ profile hashes, per-config install trees, `.wyvrnignore`). This agent removes
35
+ the "I don't know where to start" barrier so teammates can adopt the tool
36
+ without reading the full docs first.
37
+
38
+ ### Prerequisites
39
+
40
+ - **wyvrnpm installed:** `npm i -g wyvrnpm` (Node.js ≥ 18). The agent checks
41
+ this first and stops if it's missing.
42
+ - **For Mode A (consume):** your team's install source URL (an S3 bucket, HTTPS
43
+ endpoint, or file/SMB share). The agent will configure it for you if it isn't
44
+ already — wyvrnpm has no default registry, so this is required to fetch
45
+ anything.
46
+ - **Claude Code**, with the agent installed (see below).
47
+
48
+ ### Install / make it active
49
+
50
+ The agent file is a single Markdown file with YAML frontmatter. Put a copy where
51
+ Claude Code looks for agents:
52
+
53
+ The supported path is the wyvrnpm CLI — `install-skill` installs the bundled
54
+ agent(s) alongside the skill:
55
+
56
+ ```bash
57
+ npm i -g wyvrnpm
58
+ wyvrnpm install-skill --claude # installs skill -> ~/.claude/skills/
59
+ # AND agent(s) -> ~/.claude/agents/
60
+ wyvrnpm install-skill --claude --force # overwrite after upgrading wyvrnpm
61
+ ```
62
+
63
+ Or copy the file by hand:
64
+
65
+ ```bash
66
+ # Personal — available in every project on your machine:
67
+ mkdir -p ~/.claude/agents
68
+ cp claude/agents/wyvrnpm-onboarding.md ~/.claude/agents/
69
+
70
+ # OR project-local — shared with everyone who clones that repo:
71
+ mkdir -p <your-repo>/.claude/agents
72
+ cp claude/agents/wyvrnpm-onboarding.md <your-repo>/.claude/agents/
73
+ ```
74
+
75
+ Claude Code picks it up on the next session start.
76
+
77
+ ### How to use it
78
+
79
+ **Implicitly** — just describe an onboarding task while you're in a C++ repo and
80
+ Claude routes to the agent:
81
+
82
+ - "Set up wyvrnpm in this project."
83
+ - "Add wyvrnpm and pull in `fmt` and `spdlog`."
84
+ - "Convert this library into a wyvrnpm package."
85
+ - "Make this repo publishable on our registry."
86
+ - "How do I start with wyvrnpm here?"
87
+
88
+ **Explicitly** — name it: *"Use the wyvrnpm-onboarding agent to set this up."*
89
+
90
+ If it can't tell whether you want Mode A or Mode B, it asks one short question
91
+ rather than guessing.
92
+
93
+ ### What you get back
94
+
95
+ Every run ends with:
96
+
97
+ 1. A summary — which mode it ran, the files it created/changed, and what it
98
+ **verified** (e.g. "install OK; all 4 configs built + installed").
99
+ 2. The single next command to run yourself — for Mode B that's the exact
100
+ `wyvrnpm publish …` line plus the pre-publish checklist; for Mode A it's
101
+ "you're ready to build."
102
+ 3. If something failed: the specific error and how it was fixed, or what's still
103
+ blocked. It won't claim success on an unvalidated manifest.
104
+
105
+ ### Safety — it never writes to your team's registry
106
+
107
+ Onboarding is meant to be safe to run. The agent will **not** run any command
108
+ that writes to shared team state without your explicit confirmation:
109
+
110
+ - `wyvrnpm publish` / `publish --force` — it always stops here and hands the
111
+ command back to you.
112
+ - `wyvrnpm install --upload-built` — also a registry write.
113
+ - `wyvrnpm configure remove-source`.
114
+
115
+ Everything else it runs is local and reversible (`install`, `build`, `clean`,
116
+ `init`, `bootstrap`, `show`, `link`, profile/source *reads*). For
117
+ `configure add-source` it shows you the URL/credentials before writing them.
118
+
119
+ ### Example
120
+
121
+ ```text
122
+ You (in an app repo): "set up wyvrnpm here and add fmt"
123
+
124
+ Agent:
125
+ • wyvrnpm --version → 2.21.0 ✓
126
+ • No install source configured — what's your team's registry URL?
127
+ You: https://pkg.example.com/cpp
128
+ Agent:
129
+ • configure add-source --kind install --name primary --url https://pkg.example.com/cpp
130
+ • profile detect → saved "default" (Windows / x86_64 / msvc 193 / C++20)
131
+ • wyvrnpm init → wyvrn.json (kind: ConsoleApp)
132
+ • wyvrnpm add fmt → "^10.x"
133
+ • wyvrnpm install → downloaded fmt, generated toolchain + CMakePresets.json
134
+ • patched CMakeLists.txt: wyvrnpm_find_dependencies();
135
+ target_link_libraries(MyApp PRIVATE wyvrn::fmt); wyvrnpm_finalize_targets()
136
+ • cmake --preset wyvrn-default → configured OK
137
+
138
+ Summary: consuming mode. fmt wired in and the build configures.
139
+ Next: wyvrnpm build
140
+ ```
141
+
142
+ ### Customizing
143
+
144
+ The agent is just `wyvrnpm-onboarding.md`. Edit the frontmatter to change:
145
+
146
+ - `model:` — defaults to `sonnet` (cheap/fast for procedural work); bump to
147
+ `opus` for trickier conversions.
148
+ - `tools:` — the allowlist (`Read, Grep, Glob, Edit, Write, Bash, Skill`).
149
+
150
+ The body is the system prompt; it loads the `wyvrnpm` skill for authoritative
151
+ command and manifest detail, so keep the two in sync.
152
+
153
+ ### Out of scope
154
+
155
+ It deliberately does **not**: publish for you, set up CI pipelines, or handle
156
+ cross-compilation / recipe revisions (features not yet shipped). For deep
157
+ manifest, command, or diagnostic questions it defers to the `wyvrnpm` skill and
158
+ the user docs (README / the Confluence "Wyvrn Package Manager" and "WyvrnPM
159
+ Cmake Utils" pages).
@@ -0,0 +1,131 @@
1
+ ---
2
+ name: wyvrnpm-onboarding
3
+ description: >-
4
+ Onboard a C++ project onto wyvrnpm. Use this agent when someone wants to adopt
5
+ wyvrnpm and is not yet familiar with it — either (A) SET UP a new or existing
6
+ project to CONSUME wyvrnpm dependencies, or (B) CONVERT an existing CMake
7
+ project into a publishable wyvrnpm package. Triggers on "set up wyvrnpm here",
8
+ "add wyvrnpm to my project", "use wyvrnpm for my deps", "convert this to
9
+ wyvrnpm", "make this a wyvrnpm package", "how do I start with wyvrnpm", or a
10
+ teammate opening a C++ repo that has no wyvrn.json yet and asking to get going.
11
+ Drives the end-to-end flow — manifest, install, CMake wiring, and per-config
12
+ build verification — and STOPS before any registry-writing publish.
13
+ tools: Read, Grep, Glob, Edit, Write, Bash, Skill
14
+ model: sonnet
15
+ ---
16
+
17
+ You are the wyvrnpm onboarding guide. Your job is to get a C++ developer who is
18
+ **new to wyvrnpm** productive: either consuming dependencies through it, or
19
+ turning their project into a publishable wyvrnpm package. Assume the person does
20
+ not know wyvrnpm yet — explain briefly as you go, and prefer running the safe
21
+ commands for them over telling them to run things.
22
+
23
+ wyvrnpm is a private, registry-less C++ package manager (a Node.js CLI,
24
+ `npm i -g wyvrnpm`). It publishes/consumes prebuilt C++ artefacts through static
25
+ hosting (S3 / HTTP / local dir / SMB). Every artefact is addressed by
26
+ `(name, version, profileHash)` where the hash encodes the toolchain profile +
27
+ package options, so ABI compatibility is explicit. Versions are 4-part
28
+ `major.minor.patch.build`.
29
+
30
+ ## First, always
31
+
32
+ 1. **Load the `wyvrnpm` skill** (invoke it via the Skill tool). It is the
33
+ authoritative reference for manifest fields, commands, and diagnostics. This
34
+ prompt is the orchestration; the skill is the detail. When the skill and this
35
+ prompt disagree, trust the skill, then the live tool output.
36
+ 2. **Check the tool is installed:** run `wyvrnpm --version`. If it's missing,
37
+ tell them to `npm i -g wyvrnpm` (Node ≥ 18) and stop until it's available.
38
+ 3. **Detect the mode** (see below). If it's genuinely ambiguous, ask one short
39
+ question — do not guess.
40
+
41
+ ## Pick the mode
42
+
43
+ There are two distinct goals. Figure out which one applies before doing anything:
44
+
45
+ - **A — CONSUME** (most common for app/teammate onboarding): "I want to *use*
46
+ C++ libraries from our team registry in this project." → you'll add
47
+ `dependencies`, run `install`, and wire CMake so `find_package` works.
48
+ - **B — CONVERT/PUBLISH**: "I want to turn *this* library into a wyvrnpm package
49
+ others can depend on." → you'll write a `kind` + `build` recipe + options,
50
+ build all configs, and hand back a ready-to-run `publish` (you never publish).
51
+
52
+ Signals: a project with `add_executable` that wants third-party libs → usually A.
53
+ An existing OSS/library repo (`add_library`, has a public `include/`) they want
54
+ to share → usually B. If both could apply, ask.
55
+
56
+ ## Mode A — set up a project to consume wyvrnpm deps
57
+
58
+ 1. **Install source.** Run `wyvrnpm configure list`. If no install source is
59
+ configured, explain that wyvrnpm has no default registry and ask them for the
60
+ team's source URL, then `wyvrnpm configure add-source --kind install --name
61
+ <name> --url <url>` (show the URL/credentials before writing). Without a
62
+ source, `install` can't fetch anything.
63
+ 2. **Profile.** Run `wyvrnpm configure profile show`. If none is saved, run
64
+ `wyvrnpm configure profile detect` (saves "default") so artefacts resolve to
65
+ their machine's toolchain.
66
+ 3. **Manifest.** If there's no `wyvrn.json`, run `wyvrnpm init` (or copy a
67
+ per-kind template from the skill's `templates/`). Set `name`, a 4-part
68
+ `version`, and `kind` (usually `ConsoleApp`).
69
+ 4. **Add deps.** For each library they want, `wyvrnpm add <name>` (resolves
70
+ latest, writes a caret range) or `wyvrnpm add <name>@<version>`.
71
+ 5. **Install.** `wyvrnpm install`. This downloads to `wyvrn_internal/`, writes
72
+ `wyvrn.lock`, and generates the CMake toolchain + `CMakePresets.json`.
73
+ 6. **Wire CMake.** In their top-level `CMakeLists.txt`, after `project()`:
74
+ call `wyvrnpm_find_dependencies()`, then `target_link_libraries(<tgt> PRIVATE
75
+ wyvrn::<dep> ...)`, and `wyvrnpm_finalize_targets()` at the end (copies
76
+ runtime DLLs on Windows). Configure via the generated preset:
77
+ `cmake --preset wyvrn-default` then `wyvrnpm build`. For the full toolchain
78
+ model and the `WYVRN_*` variables/macros, point them at the team's
79
+ "WyvrnPM Cmake Utils" doc.
80
+ 7. **gitignore.** Ensure `wyvrn_internal/`, `wyvrn.lock`, `wyvrn.local.json`,
81
+ `build/`, `*.zip` are ignored.
82
+ 8. **Verify** the build configures and links, then summarize.
83
+
84
+ ## Mode B — convert an existing CMake project into a wyvrnpm package
85
+
86
+ Follow the skill's "convert-a-project flow" exactly. The deliverable is a
87
+ **validated** package, not just a `wyvrn.json`:
88
+
89
+ 1. **Read the existing `CMakeLists.txt`** to choose `kind`
90
+ (`HeaderOnlyLib` / `StaticLib` / `DynamicLib`), declare `options` only for
91
+ real ABI-affecting toggles, and write a `build` recipe. For known OSS,
92
+ `wyvrnpm bootstrap <git-url>` drafts a first-pass manifest (clones, detects
93
+ kind, applies cookbook flags) — review and correct it.
94
+ 2. **`build.configs` defaults to all four:**
95
+ `["Debug","Release","RelWithDebInfo","MinSizeRel"]` (note the spelling —
96
+ `RelWithDebInfo`). Restrict only with a concrete reason.
97
+ 3. **Write `.wyvrnignore`** next to `wyvrn.json` — `publish` zips the whole
98
+ project tree, so exclude tests/examples/docs/`build/`/CI. A HeaderOnlyLib zip
99
+ should be well under 1 MB.
100
+ 4. **`wyvrnpm install`** in the project root — surfaces manifest errors fast.
101
+ 5. **`wyvrnpm build --install --all-configs`** — `--install` is mandatory; plain
102
+ build doesn't populate the install tree (headers + libs + `*Config.cmake`)
103
+ that consumers' `find_package` needs. All four configs must complete.
104
+ 6. **Diagnose any failure** (skill's diagnostics reference) and fix the manifest
105
+ — don't paper over a broken config.
106
+ 7. **STOP before `wyvrnpm publish`.** Hand back the exact `publish` command plus
107
+ the pre-publish checklist (changes committed, branch pushed, right profile,
108
+ `--source` explicit). The human runs it.
109
+
110
+ ## Hard safety rails (never violate)
111
+
112
+ These write to **shared team state** and need the human's explicit yes — never
113
+ run them yourself as part of onboarding:
114
+
115
+ - `wyvrnpm publish` / `publish --force` — registry writes other teammates see.
116
+ - `wyvrnpm install --upload-built` — also a registry write.
117
+ - `wyvrnpm configure remove-source` — confirm first.
118
+
119
+ Safe to run without asking (local, reversible): `install` (no `--upload-built`),
120
+ `build`, `clean`, `show`, `link`/`unlink`, `init`, `bootstrap`, `configure list`
121
+ / `profile show` / `profile detect`. For `configure add-source`, show the URL +
122
+ any credentials before writing.
123
+
124
+ ## Finish every run with
125
+
126
+ - A short summary: which mode you ran, the files you created/changed, and what
127
+ you verified (e.g. "install OK; all 4 configs built + installed").
128
+ - The single exact next command the human should run (for Mode B, the
129
+ `wyvrnpm publish ...` line), or "you're ready to build" for Mode A.
130
+ - If anything failed, the specific error and what you changed to fix it (or what
131
+ remains blocked). Be honest — an unvalidated manifest is worth less than none.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wyvrnpm",
3
- "version": "2.20.2",
3
+ "version": "2.21.1",
4
4
  "description": "A simple, static-hosting-compatible C++ package manager",
5
5
  "keywords": [
6
6
  "c++",
@@ -21,6 +21,7 @@
21
21
  "src/",
22
22
  "cmake/",
23
23
  "claude/skills/wyvrnpm.skill",
24
+ "claude/agents/",
24
25
  "README.md"
25
26
  ],
26
27
  "scripts": {
@@ -11,16 +11,19 @@ const log = require('../logger');
11
11
  // ---------------------------------------------------------------------------
12
12
  // `wyvrnpm install-skill --claude`
13
13
  //
14
- // Copies the bundled wyvrnpm Agent Skill (claude/skills/wyvrnpm.skill) into
15
- // the user's Claude Code skills directory so Claude picks it up automatically.
14
+ // Copies the bundled wyvrnpm Agent Skill (claude/skills/wyvrnpm.skill) AND the
15
+ // bundled onboarding agents (claude/agents/*.md) into the user's Claude Code
16
+ // directories so Claude picks them up automatically.
16
17
  //
17
18
  // Works for both install shapes:
18
- // - `npm i -g wyvrnpm` → skill zip ships under <pkg>/claude/skills/
19
+ // - `npm i -g wyvrnpm` → assets ship under <pkg>/claude/
19
20
  // - `git clone` of this repo → same path, just not from node_modules
20
21
  //
21
- // Destination, per target:
22
- // --claude → %USERPROFILE%\.claude\skills\wyvrnpm\ (Windows)
23
- // ~/.claude/skills/wyvrnpm/ (macOS / Linux)
22
+ // Destinations, per target (--claude):
23
+ // skill → %USERPROFILE%\.claude\skills\wyvrnpm\ (Windows)
24
+ // ~/.claude/skills/wyvrnpm/ (macOS / Linux)
25
+ // agents → %USERPROFILE%\.claude\agents\ (Windows)
26
+ // ~/.claude/agents/ (macOS / Linux)
24
27
  // ---------------------------------------------------------------------------
25
28
 
26
29
  const SKILL_NAME = 'wyvrnpm';
@@ -31,18 +34,94 @@ function bundledSkillPath() {
31
34
  return path.resolve(__dirname, '..', '..', 'claude', 'skills', `${SKILL_NAME}.skill`);
32
35
  }
33
36
 
37
+ function bundledAgentsDir() {
38
+ // __dirname = <pkg>/src/commands; agent definitions live at
39
+ // <pkg>/claude/agents/*.md (README.md excluded — it's docs, not an agent).
40
+ return path.resolve(__dirname, '..', '..', 'claude', 'agents');
41
+ }
42
+
34
43
  function claudeSkillsDir() {
35
44
  const home = os.homedir();
36
45
  if (!home) throw new Error('cannot resolve user home directory');
37
46
  return path.join(home, '.claude', 'skills');
38
47
  }
39
48
 
49
+ function claudeAgentsDir() {
50
+ const home = os.homedir();
51
+ if (!home) throw new Error('cannot resolve user home directory');
52
+ return path.join(home, '.claude', 'agents');
53
+ }
54
+
55
+ /**
56
+ * Bundled agent-definition files to install — every `*.md` under
57
+ * `claude/agents/` except `README.md` (which is human docs, not an agent
58
+ * definition and would parse as a nameless agent). Returns absolute paths.
59
+ *
60
+ * @returns {string[]}
61
+ */
62
+ function bundledAgentFiles() {
63
+ const dir = bundledAgentsDir();
64
+ if (!fs.existsSync(dir)) return [];
65
+ return fs.readdirSync(dir)
66
+ .filter((f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md')
67
+ .sort()
68
+ .map((f) => path.join(dir, f));
69
+ }
70
+
40
71
  function rmrf(target) {
41
72
  if (!fs.existsSync(target)) return;
42
73
  fs.rmSync(target, { recursive: true, force: true });
43
74
  }
44
75
 
76
+ /**
77
+ * Install the bundled onboarding agents into ~/.claude/agents/. Unlike the
78
+ * skill, agents are independent plain-Markdown files, so this never aborts
79
+ * the run: an already-present agent is skipped (with a hint) unless --force,
80
+ * and the rest still install. Dry-run reports without writing.
81
+ *
82
+ * @param {{ force: boolean, dryRun: boolean }} opts
83
+ */
84
+ function installAgentsForClaude({ force, dryRun }) {
85
+ const agentFiles = bundledAgentFiles();
86
+ if (agentFiles.length === 0) {
87
+ log.info('No bundled agents to install.');
88
+ return;
89
+ }
90
+
91
+ const destDir = claudeAgentsDir();
92
+ log.info(`Agents : ${destDir}`);
93
+
94
+ if (dryRun) {
95
+ for (const src of agentFiles) {
96
+ log.info(`(--dry-run) would install agent ${path.basename(src)} → ${destDir}`);
97
+ }
98
+ return;
99
+ }
100
+
101
+ fs.mkdirSync(destDir, { recursive: true });
102
+ let installed = 0;
103
+ for (const src of agentFiles) {
104
+ const name = path.basename(src);
105
+ const dest = path.join(destDir, name);
106
+ if (fs.existsSync(dest) && !force) {
107
+ log.warn(`agent ${name} already installed — re-run with --force to overwrite`);
108
+ continue;
109
+ }
110
+ fs.copyFileSync(src, dest);
111
+ installed += 1;
112
+ log.success(`Installed agent ${name} → ${dest}`);
113
+ }
114
+ if (installed > 0) {
115
+ log.info('Claude Code will pick up the agent(s) on next session start.');
116
+ }
117
+ }
118
+
45
119
  function installForClaude({ force, dryRun }) {
120
+ installSkillForClaude({ force, dryRun });
121
+ installAgentsForClaude({ force, dryRun });
122
+ }
123
+
124
+ function installSkillForClaude({ force, dryRun }) {
46
125
  const zipPath = bundledSkillPath();
47
126
  if (!fs.existsSync(zipPath)) {
48
127
  log.error(`bundled skill not found at ${zipPath}`);
@@ -111,5 +190,8 @@ async function installSkill(argv) {
111
190
  }
112
191
 
113
192
  module.exports = installSkill;
114
- module.exports.bundledSkillPath = bundledSkillPath;
115
- module.exports.claudeSkillsDir = claudeSkillsDir;
193
+ module.exports.bundledSkillPath = bundledSkillPath;
194
+ module.exports.claudeSkillsDir = claudeSkillsDir;
195
+ module.exports.bundledAgentsDir = bundledAgentsDir;
196
+ module.exports.claudeAgentsDir = claudeAgentsDir;
197
+ module.exports.bundledAgentFiles = bundledAgentFiles;
@@ -8,6 +8,9 @@ const { createLink, isLink, removeLink, getLinkTarget } = require('../link-utils
8
8
  const { downloadDependencies } = require('../download');
9
9
  const { resolveDependencies } = require('../resolve');
10
10
  const { wyvrnFetch } = require('../http-fetch');
11
+ const { resolveBinaryDirRoot } = require('../binary-dir');
12
+ const { readLocalOverlay } = require('../conf');
13
+ const { hasBuildRecipe, DEFAULT_RECIPE } = require('../build/recipe');
11
14
  const log = require('../logger');
12
15
 
13
16
  /**
@@ -37,14 +40,51 @@ function getEffectivePath(entry) {
37
40
  return entry.path;
38
41
  }
39
42
 
43
+ /**
44
+ * Compute the install-tree subdirectory for a package that declares a build
45
+ * recipe, relative to the package root. Mirrors how `publish` locates the
46
+ * install tree it overlays: `<binaryDirRoot>/wyvrn-<profile>/<installDir>`.
47
+ *
48
+ * The returned path is forward-slash separated and relative — `getEffectivePath`
49
+ * joins it onto the package root to yield the absolute install dir.
50
+ *
51
+ * @param {object} manifest - Parsed wyvrn.json (must have a build recipe).
52
+ * @param {object} config - Resolved config (for defaultProfile fallback).
53
+ * @param {object} argv - Parsed yargs arguments (reads `profile`).
54
+ * @param {string} packagePath - Absolute package root (for the local overlay).
55
+ * @returns {string} Relative, forward-slash install-tree subdir.
56
+ */
57
+ function installTreeSubdir(manifest, config, argv, packagePath) {
58
+ const overlay = readLocalOverlay(packagePath);
59
+ const binaryDirRoot = resolveBinaryDirRoot({
60
+ localOverlayBinaryDirRoot: overlay.binaryDirRoot,
61
+ });
62
+ const profileName =
63
+ typeof argv.profile === 'string' && argv.profile.length > 0
64
+ ? argv.profile
65
+ : config.defaultProfile || 'default';
66
+ const installDir = manifest.build.installDir ?? DEFAULT_RECIPE.installDir;
67
+ return [binaryDirRoot, `wyvrn-${profileName}`, installDir].join('/');
68
+ }
69
+
40
70
  /**
41
71
  * Register the current directory's package globally for linking.
42
72
  * Reads wyvrn.json to get the package name, stores {name: {path, subdir}} in config.
43
73
  *
74
+ * When the package declares a build recipe, the registered target is its
75
+ * install tree (`<binaryDirRoot>/wyvrn-<profile>/<installDir>`) rather than the
76
+ * raw source root. That tree carries the installed headers, libraries, and
77
+ * `<name>Config.cmake` at its root — the exact layout a consumer's
78
+ * `find_package(<name> CONFIG)` expects after the link lands in
79
+ * `wyvrn_internal/<name>/`. Linking the source root would bury the CMake
80
+ * config under `build/` and find_package would miss it, so the link must
81
+ * simulate the packaged artefact, not the working tree. An explicit
82
+ * `--subdir` always wins (monorepo: link a sub-package's own tree).
83
+ *
44
84
  * @param {string} manifestPath - Path to wyvrn.json
45
- * @param {string} [subdir] - Optional subdirectory within the package to link.
85
+ * @param {object} argv - Parsed yargs arguments (reads `subdir`, `profile`).
46
86
  */
47
- function registerGlobal(manifestPath, subdir) {
87
+ function registerGlobal(manifestPath, argv) {
48
88
  const manifest = readManifest(manifestPath);
49
89
  const packageName = manifest.name || manifest.Name;
50
90
 
@@ -58,14 +98,25 @@ function registerGlobal(manifestPath, subdir) {
58
98
  config.linkedPackages = config.linkedPackages || {};
59
99
 
60
100
  const entry = { path: packagePath };
61
- if (subdir) {
62
- entry.subdir = subdir;
101
+ let installTreeLinked = false;
102
+ if (argv.subdir) {
103
+ entry.subdir = argv.subdir;
104
+ } else if (hasBuildRecipe(manifest)) {
105
+ entry.subdir = installTreeSubdir(manifest, config, argv, packagePath);
106
+ installTreeLinked = true;
63
107
  }
64
108
  config.linkedPackages[packageName] = entry;
65
109
  writeConfig(config);
66
110
 
67
111
  const effectivePath = getEffectivePath(entry);
68
112
  log.info(`Registered "${packageName}" → ${effectivePath}`);
113
+ if (installTreeLinked && !fs.existsSync(effectivePath)) {
114
+ log.warn(
115
+ `install tree not found at ${effectivePath}\n` +
116
+ ` Build + install it so consumers' find_package(${packageName}) resolves:\n` +
117
+ ` wyvrnpm build --all-configs --install`,
118
+ );
119
+ }
69
120
  }
70
121
 
71
122
  /**
@@ -261,7 +312,7 @@ async function link(argv) {
261
312
  log.error(`no wyvrn.json found at ${manifestPath}`);
262
313
  process.exit(1);
263
314
  }
264
- registerGlobal(manifestPath, argv.subdir);
315
+ registerGlobal(manifestPath, argv);
265
316
  return;
266
317
  }
267
318