wyvrnpm 2.0.4 → 2.3.2

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 (44) hide show
  1. package/README.md +639 -5
  2. package/bin/wyvrn.js +220 -10
  3. package/claude/skills/wyvrnpm.skill +0 -0
  4. package/cmake/cpp.cmake +9 -0
  5. package/cmake/functions.cmake +224 -0
  6. package/cmake/macros.cmake +233 -0
  7. package/cmake/options.cmake +23 -0
  8. package/cmake/variables.cmake +171 -0
  9. package/package.json +3 -1
  10. package/src/build/cache.js +148 -0
  11. package/src/build/clone.js +170 -0
  12. package/src/build/cmake.js +342 -0
  13. package/src/build/index.js +275 -0
  14. package/src/build/msvc-env.js +217 -0
  15. package/src/build/recipe.js +155 -0
  16. package/src/commands/build.js +283 -0
  17. package/src/commands/clean.js +56 -16
  18. package/src/commands/configure.js +6 -5
  19. package/src/commands/init.js +3 -2
  20. package/src/commands/install-skill.js +107 -0
  21. package/src/commands/install.js +262 -19
  22. package/src/commands/link.js +18 -15
  23. package/src/commands/profile.js +15 -12
  24. package/src/commands/publish.js +216 -65
  25. package/src/commands/show.js +237 -0
  26. package/src/compat.js +261 -0
  27. package/src/config.js +3 -1
  28. package/src/download.js +431 -87
  29. package/src/ignore.js +118 -0
  30. package/src/logger.js +94 -0
  31. package/src/manifest.js +12 -7
  32. package/src/options.js +303 -0
  33. package/src/profile.js +56 -4
  34. package/src/providers/base.js +16 -1
  35. package/src/providers/file.js +12 -6
  36. package/src/providers/http.js +15 -10
  37. package/src/providers/s3.js +14 -9
  38. package/src/resolve.js +179 -19
  39. package/src/toolchain/deps.js +164 -0
  40. package/src/toolchain/index.js +141 -0
  41. package/src/toolchain/presets.js +263 -0
  42. package/src/toolchain/template.cmake +66 -0
  43. package/src/upload-built.js +256 -0
  44. package/src/version-range.js +301 -0
@@ -8,6 +8,19 @@ const { resolveDependencies } = require('../resolv
8
8
  const { downloadDependencies } = require('../download');
9
9
  const { readConfig } = require('../config');
10
10
  const { loadProfile, hashProfile, formatProfile, mergeProfile } = require('../profile');
11
+ const { generateToolchain, generateCMakePresets } = require('../toolchain');
12
+ const { normalizeRecipe, hasBuildRecipe } = require('../build/recipe');
13
+ const {
14
+ normalizeOptionsDeclaration,
15
+ resolveEffectiveOptions,
16
+ parseCliOptions,
17
+ } = require('../options');
18
+ const {
19
+ resolveUploadDestination,
20
+ createUploadStats,
21
+ formatUploadSummary,
22
+ } = require('../upload-built');
23
+ const log = require('../logger');
11
24
 
12
25
  /**
13
26
  * Resolves and downloads all dependencies declared in the manifest, then writes
@@ -23,6 +36,32 @@ const { loadProfile, hashProfile, formatProfile, mergeProfile } = require('../pr
23
36
  async function install(argv) {
24
37
  const manifestPath = path.resolve(argv.manifest);
25
38
  const rootDir = path.resolve(argv.root);
39
+ const buildMode = argv.build ?? 'never';
40
+ const uploadBuilt = argv.uploadBuilt === true;
41
+ const jsonOut = argv.format === 'json';
42
+
43
+ // Under --format=json, stdout is reserved for the final JSON payload.
44
+ // All info/success lines route to stderr via the logger.
45
+ if (jsonOut) log.setJsonMode(true);
46
+
47
+ // --upload-built implies a build step happened. `--build=never` +
48
+ // `--upload-built` is a usage error — fail fast rather than silently
49
+ // accept something that cannot upload anything.
50
+ if (uploadBuilt && buildMode === 'never') {
51
+ log.error(
52
+ '--upload-built requires --build=missing (or --build=always).\n' +
53
+ ' Nothing will be built, so there is nothing to upload.',
54
+ );
55
+ process.exit(1);
56
+ }
57
+
58
+ if (buildMode === 'always') {
59
+ log.error(
60
+ '--build=always is not implemented (requires PLAN-BUILD-MISSING phase 3).\n' +
61
+ ' Use --build=never (default) or --build=missing.',
62
+ );
63
+ process.exit(1);
64
+ }
26
65
 
27
66
  // ── Auth / sources ────────────────────────────────────────────────────────
28
67
  let packageSources;
@@ -32,13 +71,13 @@ async function install(argv) {
32
71
  const config = readConfig();
33
72
  packageSources = config.installSources.map((s) => s.url);
34
73
  if (packageSources.length > 0) {
35
- console.log(`[wyvrn] Using ${packageSources.length} source(s) from config`);
74
+ log.info(`Using ${packageSources.length} source(s) from config`);
36
75
  }
37
76
  }
38
77
 
39
78
  if (packageSources.length === 0) {
40
- console.warn(
41
- '[wyvrn] Warning: no sources configured. ' +
79
+ log.warn(
80
+ 'no sources configured. ' +
42
81
  'Pass --source <url> or run: wyvrnpm configure add-source --kind install ...',
43
82
  );
44
83
  }
@@ -48,7 +87,7 @@ async function install(argv) {
48
87
  const profileName = argv.profile ?? config.defaultProfile ?? 'default';
49
88
  const { profile: baseProfile, fromFile } = loadProfile(profileName);
50
89
 
51
- console.log(`[wyvrn] Profile: "${profileName}"${fromFile ? '' : ' (auto-detected)'}`);
90
+ log.info(`Profile: "${profileName}"${fromFile ? '' : ' (auto-detected)'}`);
52
91
  console.log(formatProfile(baseProfile).split('\n').map((l) => ` ${l}`).join('\n'));
53
92
  console.log(` Profile hash : ${hashProfile(baseProfile)}`);
54
93
 
@@ -63,9 +102,9 @@ async function install(argv) {
63
102
  const ver = typeof entry === 'string' ? entry : entry.version;
64
103
  if (ver) lockedVersions.set(name, ver);
65
104
  }
66
- console.log(`[wyvrn] Lock file found — ${lockedVersions.size} pinned package(s)`);
105
+ log.info(`Lock file found — ${lockedVersions.size} pinned package(s)`);
67
106
  } catch {
68
- console.warn('[wyvrn] Warning: could not read wyvrn.lock, resolving from scratch');
107
+ log.warn('could not read wyvrn.lock, resolving from scratch');
69
108
  }
70
109
  }
71
110
 
@@ -80,46 +119,112 @@ async function install(argv) {
80
119
  Object.entries(normalizedDeps).map(([name, d]) => [name, d.version]),
81
120
  );
82
121
 
83
- console.log(
84
- `[wyvrn] Installing dependencies for "${manifest.name}" (platform: ${argv.platform})`,
122
+ log.info(
123
+ `Installing dependencies for "${manifest.name}" (platform: ${argv.platform})`,
85
124
  );
86
125
 
126
+ // Manifests collected during resolution — reused below to read each dep's
127
+ // recipe-declared `options` block without a second HTTP round trip.
128
+ const depManifests = new Map();
129
+
87
130
  const resolvedDeps = await resolveDependencies(
88
131
  depsForResolution,
89
132
  packageSources,
90
133
  argv.platform,
91
134
  fetch,
92
135
  lockedVersions,
136
+ depManifests,
93
137
  );
94
138
 
139
+ // CLI `-o <pkg>:<name>=<value>` overrides, parsed into { pkg → {name: value} }.
140
+ const cliOptionsByPkg = parseCliOptions(argv.option);
141
+
95
142
  // ── Build per-dependency enriched map ─────────────────────────────────────
96
- // Each dep gets a profileHash based on base profile + any settings overrides
97
- // declared in wyvrn.json under that dependency's "settings" key.
143
+ // Each dep gets:
144
+ // - an effective profile (base + per-dep settings overrides)
145
+ // - effective options (recipe defaults + consumer overrides + CLI overrides)
146
+ // - profileHash computed from both (options only hashed in when declared)
98
147
  const enrichedDeps = new Map();
99
148
  for (const [name, version] of resolvedDeps) {
100
- const depOverrides = normalizedDeps[name]?.settings ?? {};
149
+ const depOverrides = normalizedDeps[name]?.settings ?? {};
101
150
  const effectiveProfile = mergeProfile(baseProfile, depOverrides);
102
- const profileHash = hashProfile(effectiveProfile);
151
+
152
+ const depManifest = depManifests.get(name);
153
+ const declaration = depManifest?.options
154
+ ? normalizeOptionsDeclaration(depManifest.options, `${name}@${version}`)
155
+ : null;
156
+ const effectiveOptions = resolveEffectiveOptions(
157
+ declaration,
158
+ normalizedDeps[name]?.options ?? {},
159
+ cliOptionsByPkg[name] ?? {},
160
+ `${name}@${version}`,
161
+ );
162
+
163
+ const profileHash = hashProfile(effectiveProfile, effectiveOptions);
103
164
 
104
165
  if (Object.keys(depOverrides).length > 0) {
105
- console.log(
106
- `[wyvrn] ${name}: settings override → profileHash ${profileHash}` +
166
+ log.info(
167
+ `${name}: settings override → profileHash ${profileHash}` +
107
168
  ` (${JSON.stringify(depOverrides)})`,
108
169
  );
109
170
  }
171
+ if (effectiveOptions) {
172
+ log.info(`${name}: options → ${JSON.stringify(effectiveOptions)}`);
173
+ }
110
174
 
111
- enrichedDeps.set(name, { version, profileHash, profile: effectiveProfile });
175
+ enrichedDeps.set(name, {
176
+ version,
177
+ profileHash,
178
+ profile: effectiveProfile,
179
+ options: effectiveOptions,
180
+ });
112
181
  }
113
182
 
114
183
  const razerDir = path.join(rootDir, 'wyvrn_internal');
115
184
 
116
185
  // Extract auth from the first install source (if any)
117
186
  const firstInstallSrc = config.installSources?.[0];
118
- const authOptions = {
187
+ const downloadOptions = {
119
188
  awsProfile: firstInstallSrc?.profile ?? undefined,
120
189
  token: firstInstallSrc?.token ?? undefined,
190
+ buildMode,
191
+ profileName,
121
192
  };
122
193
 
194
+ // ── Resolve upload destination up-front when --upload-built is set ──────
195
+ // Failing here (no publish source configured) avoids spending minutes on
196
+ // a source-build whose output we can't deliver.
197
+ if (uploadBuilt) {
198
+ const resolved = resolveUploadDestination(
199
+ {
200
+ uploadSource: argv.uploadSource,
201
+ awsProfile: argv.awsProfile ?? firstInstallSrc?.profile,
202
+ token: argv.token ?? firstInstallSrc?.token,
203
+ },
204
+ config,
205
+ );
206
+ if (!resolved) {
207
+ log.error(
208
+ '--upload-built was passed but no upload destination could be resolved.\n' +
209
+ ' Pass --upload-source <url-or-name> or configure a publish source:\n' +
210
+ ' wyvrnpm configure add-source --kind publish --name ... --url ...',
211
+ );
212
+ process.exit(1);
213
+ }
214
+ downloadOptions.uploadBuilt = true;
215
+ downloadOptions.uploadSource = resolved.uploadSource;
216
+ downloadOptions.uploadAuth = resolved.uploadAuth;
217
+ downloadOptions.uploadStats = createUploadStats();
218
+
219
+ if (resolved.uploadSource !== (packageSources[0] ?? '')) {
220
+ log.info(
221
+ `upload-built: install source(s) and upload source differ —\n` +
222
+ ` install: ${packageSources.join(', ') || '(none)'}\n` +
223
+ ` upload : ${resolved.uploadSource}`,
224
+ );
225
+ }
226
+ }
227
+
123
228
  const lockEntries = await downloadDependencies(
124
229
  enrichedDeps,
125
230
  packageSources,
@@ -127,7 +232,7 @@ async function install(argv) {
127
232
  razerDir,
128
233
  fetch,
129
234
  argv.timeout,
130
- authOptions,
235
+ downloadOptions,
131
236
  );
132
237
 
133
238
  // ── Write v2 lock file ────────────────────────────────────────────────────
@@ -147,10 +252,148 @@ async function install(argv) {
147
252
  };
148
253
 
149
254
  fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2) + '\n', 'utf8');
150
- console.log(`[wyvrn] Lock file written to ${lockPath}`);
255
+ log.info(`Lock file written to ${lockPath}`);
256
+
257
+ // ── CMake toolchain generation ────────────────────────────────────────────
258
+ const packageNames = [...resolvedDeps.keys()].sort();
259
+ const { toolchain } = generateToolchain({
260
+ destDir: razerDir,
261
+ profile: baseProfile,
262
+ profileName,
263
+ profileHash: hashProfile(baseProfile),
264
+ packageNames,
265
+ });
266
+ log.info(`CMake toolchain written to ${toolchain}`);
267
+
268
+ // ── CMakePresets.json emission ────────────────────────────────────────────
269
+ const toolchainRelPath = path
270
+ .relative(rootDir, toolchain)
271
+ .replace(/\\/g, '/');
272
+
273
+ // Derive generator + cacheVariables from the project's own `build` recipe
274
+ // so `wyvrnpm build` locally honours the same knobs the source-build /
275
+ // publish paths use. Without this, `build.configure` is advisory metadata
276
+ // and CMake falls back to its default generator (VS on Windows) — which
277
+ // breaks publishers whose recipe disables example/test subprojects (F15).
278
+ let presetGenerator = null;
279
+ let presetCacheVariables = null;
280
+ let presetInstallDir = null;
281
+ if (hasBuildRecipe(manifest)) {
282
+ try {
283
+ // Resolve the project's own options to expand ${options.*} in its
284
+ // configure args. When the project declares no options, this is null
285
+ // and the recipe's configure / buildArgs must not contain templates
286
+ // (normalizeRecipe enforces that).
287
+ const projectDeclaration = manifest.options
288
+ ? normalizeOptionsDeclaration(manifest.options, manifest.name)
289
+ : null;
290
+ const projectCliOverrides = cliOptionsByPkg[manifest.name] ?? {};
291
+ const projectEffectiveOptions = resolveEffectiveOptions(
292
+ projectDeclaration, {}, projectCliOverrides, manifest.name,
293
+ );
294
+ const recipe = normalizeRecipe(manifest.build, projectEffectiveOptions);
295
+
296
+ if (recipe.generators.length > 0) {
297
+ // First entry in the generator fallback chain. Users whose system
298
+ // lacks the first pick can override via CMakeUserPresets.json
299
+ // (which CMake merges in automatically).
300
+ [presetGenerator] = recipe.generators;
301
+ }
302
+
303
+ // Fold -DKEY=VALUE from recipe.configure into cacheVariables. Other
304
+ // configure args (anything not matching `-DKEY=VALUE`) can't be
305
+ // expressed in a CMake preset and are silently skipped here — the
306
+ // source-build path still sees them verbatim.
307
+ const cacheVars = {};
308
+ for (const arg of recipe.configure) {
309
+ const match = arg.match(/^-D([^=]+)=(.*)$/);
310
+ if (match) cacheVars[match[1]] = match[2];
311
+ }
312
+ if (Object.keys(cacheVars).length > 0) presetCacheVariables = cacheVars;
313
+
314
+ // Propagate `build.installDir` so that `wyvrnpm build --install` lands
315
+ // under the project's build tree instead of CMake's system default
316
+ // (C:/Program Files on Windows, /usr/local on Unix). Same semantics as
317
+ // the source-build path which passes -DCMAKE_INSTALL_PREFIX explicitly.
318
+ presetInstallDir = recipe.installDir;
319
+ } catch (err) {
320
+ log.warn(
321
+ `could not derive CMake preset config from the project's build recipe: ${err.message}\n` +
322
+ ` the preset will be emitted without a generator or recipe cacheVariables. ` +
323
+ `Fix the recipe or override via CMakeUserPresets.json.`,
324
+ );
325
+ }
326
+ }
327
+
328
+ const presetResult = generateCMakePresets({
329
+ projectRoot: rootDir,
330
+ profileName,
331
+ profileHash: hashProfile(baseProfile),
332
+ toolchainRelPath,
333
+ generator: presetGenerator,
334
+ extraCacheVariables: presetCacheVariables,
335
+ installDir: presetInstallDir,
336
+ });
337
+ if (presetResult.action === 'skipped') {
338
+ log.warn(
339
+ 'could not write presets — both CMakePresets.json and ' +
340
+ 'CMakeUserPresets.json exist and are user-owned. Add this to your presets:\n' +
341
+ ` { "name": "wyvrn-${profileName}", "cacheVariables": { "CMAKE_TOOLCHAIN_FILE": "\${sourceDir}/${toolchainRelPath}" } }`,
342
+ );
343
+ } else {
344
+ log.info(
345
+ `CMake presets ${presetResult.action}: ${presetResult.path}` +
346
+ (presetResult.isUser ? ' (user presets — CMakePresets.json is user-owned)' : ''),
347
+ );
348
+ }
151
349
 
152
350
  const count = resolvedDeps.size;
153
- console.log(`[wyvrn] Done — ${count} package${count !== 1 ? 's' : ''} installed.`);
351
+ log.success(`Done — ${count} package${count !== 1 ? 's' : ''} installed.`);
352
+
353
+ // ── upload-built summary ────────────────────────────────────────────────
354
+ const uploadSummary = formatUploadSummary(downloadOptions.uploadStats);
355
+ if (uploadSummary) log.info(uploadSummary);
356
+
357
+ // ── JSON payload (--format=json) ────────────────────────────────────────
358
+ // All log.info/success above have gone to stderr thanks to setJsonMode.
359
+ // stdout gets exactly one JSON object, no trailing text.
360
+ if (jsonOut) {
361
+ const packages = [...resolvedDeps.keys()].sort().map((name) => {
362
+ const entry = lockEntries?.get(name) ?? {};
363
+ const consumerSpec = normalizedDeps[name]?.version ?? null;
364
+ const resolvedVer = entry.version ?? resolvedDeps.get(name);
365
+ const versionRange = (consumerSpec && consumerSpec !== resolvedVer) ? consumerSpec : null;
366
+ return {
367
+ name,
368
+ version: resolvedVer,
369
+ versionRange,
370
+ profileHash: entry.profileHash ?? null,
371
+ options: entry.options ?? null,
372
+ contentSha256: entry.contentSha256 ?? null,
373
+ gitSha: entry.gitSha ?? null,
374
+ resolvedFrom: entry.resolvedFrom ?? null,
375
+ };
376
+ });
377
+
378
+ const stats = downloadOptions.uploadStats;
379
+ const uploadSummaryJson = stats ? {
380
+ uploaded: stats.uploaded.length,
381
+ skipped: stats.skipped.length,
382
+ failed: stats.failed.length,
383
+ } : null;
384
+
385
+ const payload = {
386
+ command: 'install',
387
+ wyvrnpmVersion: require('../../package.json').version,
388
+ profile: baseProfile,
389
+ profileName,
390
+ profileHash: hashProfile(baseProfile),
391
+ packages,
392
+ lockFile: lockPath,
393
+ uploadSummary: uploadSummaryJson,
394
+ };
395
+ process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
396
+ }
154
397
  }
155
398
 
156
399
  module.exports = install;
@@ -7,6 +7,7 @@ const { readManifest } = require('../manifest');
7
7
  const { createLink, isLink, removeLink, getLinkTarget } = require('../link-utils');
8
8
  const { downloadDependencies } = require('../download');
9
9
  const { resolveDependencies } = require('../resolve');
10
+ const log = require('../logger');
10
11
 
11
12
  /**
12
13
  * Normalizes a linked package entry to object form.
@@ -47,7 +48,7 @@ function registerGlobal(manifestPath, subdir) {
47
48
  const packageName = manifest.name || manifest.Name;
48
49
 
49
50
  if (!packageName) {
50
- console.error('[wyvrn] Error: wyvrn.json must have a "name" field');
51
+ log.error('wyvrn.json must have a "name" field');
51
52
  process.exit(1);
52
53
  }
53
54
 
@@ -63,7 +64,7 @@ function registerGlobal(manifestPath, subdir) {
63
64
  writeConfig(config);
64
65
 
65
66
  const effectivePath = getEffectivePath(entry);
66
- console.log(`[wyvrn] Registered "${packageName}" → ${effectivePath}`);
67
+ log.info(`Registered "${packageName}" → ${effectivePath}`);
67
68
  }
68
69
 
69
70
  /**
@@ -77,7 +78,7 @@ function linkToProject(name, sourcePath, rootDir) {
77
78
  const absoluteSource = path.resolve(sourcePath);
78
79
 
79
80
  if (!fs.existsSync(absoluteSource)) {
80
- console.error(`[wyvrn] Error: source path does not exist: ${absoluteSource}`);
81
+ log.error(`source path does not exist: ${absoluteSource}`);
81
82
  process.exit(1);
82
83
  }
83
84
 
@@ -106,7 +107,7 @@ function linkToProject(name, sourcePath, rootDir) {
106
107
  lockData.generatedAt = new Date().toISOString();
107
108
  fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2) + '\n', 'utf8');
108
109
 
109
- console.log(`[wyvrn] Linked: ${name} → ${absoluteSource}`);
110
+ log.info(`Linked: ${name} → ${absoluteSource}`);
110
111
  }
111
112
 
112
113
  /**
@@ -122,18 +123,18 @@ async function unlinkFromProject(name, rootDir, restore, argv) {
122
123
  const linkPath = path.join(razerDir, name);
123
124
 
124
125
  if (!fs.existsSync(linkPath)) {
125
- console.error(`[wyvrn] Error: package "${name}" is not installed`);
126
+ log.error(`package "${name}" is not installed`);
126
127
  process.exit(1);
127
128
  }
128
129
 
129
130
  if (!isLink(linkPath)) {
130
- console.error(`[wyvrn] Error: package "${name}" is not a linked package`);
131
+ log.error(`package "${name}" is not a linked package`);
131
132
  process.exit(1);
132
133
  }
133
134
 
134
135
  // Remove the symlink
135
136
  removeLink(linkPath);
136
- console.log(`[wyvrn] Unlinked: ${name}`);
137
+ log.info(`Unlinked: ${name}`);
137
138
 
138
139
  // Update the lock file
139
140
  const lockPath = path.join(rootDir, 'wyvrn.lock');
@@ -161,7 +162,7 @@ async function unlinkFromProject(name, rootDir, restore, argv) {
161
162
  : rawDeps;
162
163
 
163
164
  if (!deps[name]) {
164
- console.warn(`[wyvrn] Warning: "${name}" is not in manifest dependencies, cannot restore`);
165
+ log.warn(`"${name}" is not in manifest dependencies, cannot restore`);
165
166
  return;
166
167
  }
167
168
 
@@ -175,7 +176,7 @@ async function unlinkFromProject(name, rootDir, restore, argv) {
175
176
  }
176
177
 
177
178
  if (packageSources.length === 0) {
178
- console.warn('[wyvrn] Warning: no sources configured, cannot restore package');
179
+ log.warn('no sources configured, cannot restore package');
179
180
  return;
180
181
  }
181
182
 
@@ -223,11 +224,11 @@ function listLinked() {
223
224
  const entries = Object.entries(linked);
224
225
 
225
226
  if (entries.length === 0) {
226
- console.log('[wyvrn] No globally registered packages');
227
+ log.info('No globally registered packages');
227
228
  return;
228
229
  }
229
230
 
230
- console.log('[wyvrn] Globally registered packages:');
231
+ log.info('Globally registered packages:');
231
232
  for (const [name, rawEntry] of entries) {
232
233
  const entry = normalizeLinkedEntry(rawEntry);
233
234
  const effectivePath = getEffectivePath(entry);
@@ -256,7 +257,7 @@ async function link(argv) {
256
257
  // wyvrnpm link (no args) - register current package globally
257
258
  if (!argv.name) {
258
259
  if (!fs.existsSync(manifestPath)) {
259
- console.error(`[wyvrn] Error: no wyvrn.json found at ${manifestPath}`);
260
+ log.error(`no wyvrn.json found at ${manifestPath}`);
260
261
  process.exit(1);
261
262
  }
262
263
  registerGlobal(manifestPath, argv.subdir);
@@ -278,9 +279,11 @@ async function link(argv) {
278
279
  const linked = config.linkedPackages || {};
279
280
 
280
281
  if (!linked[argv.name]) {
281
- console.error(`[wyvrn] Error: "${argv.name}" is not registered globally`);
282
- console.error('[wyvrn] Run "wyvrnpm link" in the package directory first, or use:');
283
- console.error(`[wyvrn] wyvrnpm link ${argv.name} <path>`);
282
+ log.error(
283
+ `"${argv.name}" is not registered globally\n` +
284
+ `Run "wyvrnpm link" in the package directory first, or use:\n` +
285
+ ` wyvrnpm link ${argv.name} <path>`,
286
+ );
284
287
  process.exit(1);
285
288
  }
286
289
 
@@ -12,6 +12,7 @@ const {
12
12
  mergeProfile,
13
13
  } = require('../profile');
14
14
  const { readConfig, writeConfig } = require('../config');
15
+ const log = require('../logger');
15
16
 
16
17
  // ---------------------------------------------------------------------------
17
18
  // list
@@ -27,8 +28,9 @@ function list() {
27
28
  const dir = getProfilesDir();
28
29
  const defName = config.defaultProfile ?? 'default';
29
30
 
30
- console.log(`[wyvrn] Profiles directory: ${dir}`);
31
- console.log(`[wyvrn] Default profile : ${defName}\n`);
31
+ log.info(`Profiles directory: ${dir}`);
32
+ log.info(`Default profile : ${defName}`);
33
+ console.log('');
32
34
 
33
35
  if (names.length === 0) {
34
36
  console.log(' (no profiles saved — run `wyvrnpm configure profile detect` to create one)');
@@ -60,12 +62,12 @@ function show(argv) {
60
62
  const stored = readProfile(name);
61
63
 
62
64
  if (stored) {
63
- console.log(`[wyvrn] Profile "${name}":`);
65
+ log.info(`Profile "${name}":`);
64
66
  console.log(formatProfile(stored));
65
67
  console.log(`\nProfile hash : ${hashProfile(stored)}`);
66
68
  } else {
67
69
  const detected = detectProfile();
68
- console.log(`[wyvrn] Profile "${name}" not found — showing auto-detected environment:`);
70
+ log.info(`Profile "${name}" not found — showing auto-detected environment:`);
69
71
  console.log(formatProfile(detected));
70
72
  console.log(`\nProfile hash : ${hashProfile(detected)}`);
71
73
  console.log(`\nRun \`wyvrnpm configure profile detect --name ${name}\` to save this.`);
@@ -85,13 +87,14 @@ function detect(argv) {
85
87
  const name = argv.name ?? config.defaultProfile ?? 'default';
86
88
  const p = detectProfile();
87
89
 
88
- console.log('[wyvrn] Detected profile:');
90
+ log.info('Detected profile:');
89
91
  console.log(formatProfile(p));
90
92
  console.log(`\nProfile hash : ${hashProfile(p)}`);
91
93
 
92
94
  if (!argv.dryRun) {
93
95
  writeProfile(name, p);
94
- console.log(`\n[wyvrn] Saved as profile "${name}".`);
96
+ console.log('');
97
+ log.success(`Saved as profile "${name}".`);
95
98
  }
96
99
  }
97
100
 
@@ -120,7 +123,7 @@ function set(argv) {
120
123
 
121
124
  writeProfile(name, updated);
122
125
 
123
- console.log(`[wyvrn] Profile "${name}" updated:`);
126
+ log.info(`Profile "${name}" updated:`);
124
127
  console.log(formatProfile(updated));
125
128
  console.log(`\nProfile hash : ${hashProfile(updated)}`);
126
129
  }
@@ -136,13 +139,13 @@ function set(argv) {
136
139
  function setDefault(argv) {
137
140
  const name = argv.name;
138
141
  if (!readProfile(name)) {
139
- console.error(`[wyvrn] Error: profile "${name}" does not exist. Create it first.`);
142
+ log.error(`profile "${name}" does not exist. Create it first.`);
140
143
  process.exit(1);
141
144
  }
142
145
  const config = readConfig();
143
146
  config.defaultProfile = name;
144
147
  writeConfig(config);
145
- console.log(`[wyvrn] Default profile set to "${name}".`);
148
+ log.info(`Default profile set to "${name}".`);
146
149
  }
147
150
 
148
151
  // ---------------------------------------------------------------------------
@@ -156,14 +159,14 @@ function setDefault(argv) {
156
159
  function del(argv) {
157
160
  const name = argv.name;
158
161
  if (name === 'default') {
159
- console.error('[wyvrn] Error: cannot delete the "default" profile.');
162
+ log.error('cannot delete the "default" profile.');
160
163
  process.exit(1);
161
164
  }
162
165
  const removed = deleteProfile(name);
163
166
  if (removed) {
164
- console.log(`[wyvrn] Profile "${name}" deleted.`);
167
+ log.info(`Profile "${name}" deleted.`);
165
168
  } else {
166
- console.error(`[wyvrn] Error: profile "${name}" not found.`);
169
+ log.error(`profile "${name}" not found.`);
167
170
  process.exit(1);
168
171
  }
169
172
  }